[gradle-1.12] 73/211: Imported Upstream version 1.4

Kai-Chung Yan seamlik-guest at moszumanska.debian.org
Wed Jul 1 14:18:09 UTC 2015


This is an automated email from the git hooks/post-receive script.

seamlik-guest pushed a commit to branch master
in repository gradle-1.12.

commit 9aa0966e537fef13822cbfc549d2baba2cd58421
Author: Damien Raude-Morvan <drazzib at debian.org>
Date:   Tue Feb 5 23:39:31 2013 +0100

    Imported Upstream version 1.4
---
 build.gradle                                       |   22 +-
 buildSrc/build.gradle                              |    4 +-
 .../main/groovy/org/gradle/build/BuildTypes.groovy |   16 +-
 .../main/groovy/org/gradle/build/JarJarJar.groovy  |   79 ++
 config/checkstyle/suppressions.xml                 |    5 +-
 gradle/buildReceipt.gradle                         |    3 +
 gradle/dependencies.gradle                         |   37 +-
 gradle/groovyProject.gradle                        |    8 +-
 gradle/idea.gradle                                 |   15 +-
 gradle/integTest.gradle                            |    8 +-
 gradle/publish.gradle                              |    7 +-
 gradle/testFixtures.gradle                         |   18 +-
 gradle/versioning.gradle                           |   89 +-
 gradle/wrapper/gradle-wrapper.properties           |    4 +-
 gradlew                                            |    4 +-
 .../internal/DefaultAnnouncerFactory.groovy        |    9 +-
 .../jdk6/AppleScriptBackedGrowlAnnouncer.groovy    |   25 +-
 .../internal/DefaultIconProviderTest.groovy        |    8 +-
 .../org/gradle/api/plugins/antlr/AntlrPlugin.java  |   10 +-
 .../org/gradle/api/plugins/antlr/AntlrTask.java    |    3 +-
 .../api/plugins/antlr/AntlrPluginTest.groovy       |    5 +-
 .../main/groovy/org/gradle/api/package-info.java   |   26 -
 .../groovy/org/gradle/api/specs/package-info.java  |   20 -
 .../api/internal/ClosureBackedActionTest.groovy    |   91 --
 .../main/java/org/gradle/api/internal/Actions.java |   36 +-
 .../org/gradle/internal/CompositeStoppable.java    |   26 +-
 .../java/org/gradle/internal/LazyIterable.java     |   33 +
 .../java/org/gradle/internal/jvm/JavaInfo.java     |    2 -
 .../src/main/java/org/gradle/internal/jvm/Jvm.java |   15 -
 .../main/java/org/gradle/util/CollectionUtils.java |   47 +-
 .../org/gradle/api/internal/ActionsTest.groovy     |   11 +
 .../org/gradle/api/internal/IoActionsTest.groovy   |    4 +-
 .../org/gradle/internal/LazyIterableTest.groovy    |   37 +
 .../org/gradle/internal/jvm/AppleJvmTest.groovy    |    6 +-
 .../groovy/org/gradle/internal/jvm/JvmTest.groovy  |    6 +-
 .../gradle/internal/os/OperatingSystemTest.groovy  |    4 +-
 .../org/gradle/util/CollectionUtilsTest.groovy     |    9 +
 .../gradle/BuildComparisonIntegrationSpec.groovy   |   10 +-
 ...Pre12CompareGradleBuildsCrossVersionSpec.groovy |   34 +-
 .../gradle/internal/GradleBuildComparison.java     |   34 +-
 .../GradleBuildOutcomeSetInferrerTest.groovy       |    6 +-
 .../GradleBuildOutcomeSetTransformerTest.groovy    |   18 +-
 ...neratedArchiveBuildOutcomeComparatorTest.groovy |    4 +-
 .../FileToArchiveEntrySetTransformerTest.groovy    |    6 +-
 ...dleBuildComparisonResultHtmlRendererTest.groovy |    6 +-
 .../quality/CheckstylePluginIntegrationTest.groovy |   18 +
 .../CodeQualityPluginIntegrationTest.groovy        |    9 +-
 .../internal/FindBugsSpecBuilderTest.groovy        |    8 +-
 .../gradle/api/plugins/quality/Checkstyle.groovy   |    9 +-
 .../api/plugins/quality/CheckstyleExtension.groovy |    7 +
 .../api/plugins/quality/CheckstylePlugin.groovy    |    3 +-
 .../api/plugins/quality/CodeNarcPlugin.groovy      |    2 +-
 .../org/gradle/api/plugins/quality/Pmd.groovy      |    2 +-
 .../plugins/quality/CheckstylePluginTest.groovy    |   15 +-
 .../api/plugins/quality/CheckstyleTest.groovy      |   41 +
 .../api/plugins/quality/FindBugsPluginTest.groovy  |  110 +-
 subprojects/core-impl/core-impl.gradle             |    5 +-
 .../ArtifactDependenciesIntegrationTest.groovy     |  659 +++++++++++
 .../ArtifactOnlyResolutionIntegrationTest.groovy   |   86 ++
 .../resolve/CacheResolveIntegrationTest.groovy     |  117 ++
 ...ModuleDependenciesResolveIntegrationTest.groovy |   70 ++
 .../DependenciesResolveIntegrationTest.java        |   46 +
 .../DependencyNotationIntegrationSpec.groovy       |  153 +++
 ...ependencyResolutionEventsIntegrationTest.groovy |   51 +
 .../DependencyResolveRulesIntegrationTest.groovy   |  599 ++++++++++
 .../resolve/FlatDirResolveIntegrationTest.groovy   |    0
 .../ProjectDependencyResolveIntegrationTest.groovy |  298 +++++
 ...ResolutionStrategySamplesIntegrationTest.groovy |   49 +
 .../ResolveCrossVersionIntegrationTest.groovy      |    0
 .../ResolvedConfigurationIntegrationTest.groovy    |    0
 ...VersionConflictResolutionIntegrationTest.groovy |  817 +++++++++++++
 ...actCacheReuseCrossVersionIntegrationTest.groovy |   41 +
 ...AliasedArtifactResolutionIntegrationTest.groovy |  205 ++++
 .../CacheReuseCrossVersionIntegrationTest.groovy   |  173 +++
 .../M3CacheReuseCrossVersionIntegrationTest.groovy |   75 ++
 .../MavenM2CacheReuseIntegrationTest.groovy        |   55 +
 .../ResolutionOverrideIntegrationTest.groovy       |  222 ++++
 ...ameCacheUsageCrossVersionIntegrationTest.groovy |   77 ++
 .../CachedChangingModulesIntegrationTest.groovy    |  232 ++++
 ...achedDependencyResolutionIntegrationTest.groovy |  220 ++++
 .../CachedMissingModulesIntegrationTest.groovy     |  264 +++++
 ...coverFromBrokenResolutionIntegrationTest.groovy |  304 +++++
 .../FileSystemResolverIntegrationTest.groovy       |    0
 .../custom/IvySFtpResolverIntegrationTest.groovy   |   82 ++
 .../custom/IvyUrlResolverIntegrationTest.groovy    |  125 ++
 .../AbstractHttpsRepoResolveIntegrationTest.groovy |  129 +++
 ...ationDependencyResolutionIntegrationTest.groovy |  219 ++++
 ...odingDependencyResolutionIntegrationTest.groovy |   49 +
 .../http/HttpProxyResolveIntegrationTest.groovy    |  154 +++
 .../http/HttpRedirectResolveIntegrationTest.groovy |   87 ++
 .../IvyBrokenRemoteResolveIntegrationTest.groovy   |  178 +++
 ...angingModuleRemoteResolveIntegrationTest.groovy |  425 +++++++
 ...amicRevisionRemoteResolveIntegrationTest.groovy |  740 ++++++++++++
 ...IvyDynamicRevisionResolveIntegrationTest.groovy |  333 ++++++
 .../ivy/IvyFileRepoResolveIntegrationTest.groovy   |  125 ++
 .../ivy/IvyHttpRepoResolveIntegrationTest.groovy   |  223 ++++
 .../ivy/IvyHttpsRepoResolveIntegrationTest.groovy  |    0
 .../resolve/ivy/IvyResolveIntegrationTest.groovy   |  144 +++
 .../maven/BadPomFileResolveIntegrationTest.groovy  |   46 +
 .../LegacyMavenRepoResolveIntegrationTest.groovy   |  210 ++++
 .../MavenDependencyResolveIntegrationTest.groovy   |  175 +++
 .../MavenDynamicResolveIntegrationTest.groovy      |  166 +++
 .../MavenFileRepoResolveIntegrationTest.groovy     |  136 +++
 .../MavenHttpRepoResolveIntegrationTest.groovy     |  243 ++++
 .../MavenHttpsRepoResolveIntegrationTest.groovy    |    0
 .../MavenLocalRepoResolveIntegrationTest.groovy    |  140 +++
 .../MavenParentPomResolveIntegrationTest.groovy    |  163 +++
 .../MavenPomPackagingResolveIntegrationTest.groovy |  247 ++++
 .../MavenSnapshotResolveIntegrationTest.groovy     |  546 +++++++++
 .../projectA-1.2-ivy.xml                           |    0
 .../projectB-1.5-ivy.xml                           |    0
 .../projectWithConfigurationHierarchy.gradle       |   58 +
 .../projectA-1.2-ivy.xml                           |    0
 .../projectB-1.5-ivy.xml                           |    0
 .../projectWithCyclesInDependencyGraph.gradle      |    0
 .../canNestModules/projectWithNestedModules.gradle |    0
 .../canUseDynamicVersions/projectA-1.2-ivy.xml     |    0
 .../canUseDynamicVersions/projectB-1.5-ivy.xml     |    0
 .../projectWithDynamicVersions.gradle              |    0
 .../projectA-1.2-ivy.xml                           |    0
 .../projectA-2.0-ivy.xml                           |    0
 .../projectB-1.5-ivy.xml                           |    0
 .../projectB-2.1.5-ivy.xml                         |    0
 .../projectWithConflicts.gradle                    |    0
 .../dependencyReportWithConflicts/settings.gradle  |    0
 .../projectWithUnknownDependency.gradle            |    0
 .../shared/clientStore                             |  Bin
 .../shared/serverStore                             |  Bin
 .../artifacts/ArtifactDependencyResolver.java      |    0
 .../DefaultDependencyManagementServices.java       |   92 +-
 .../artifacts/DefaultModuleIdentifier.java         |   67 ++
 .../artifacts/DefaultModuleVersionIdentifier.java  |   86 ++
 .../artifacts/DefaultModuleVersionSelector.java    |  108 ++
 .../ModuleVersionIdentifierSerializer.java         |   41 +
 .../artifacts/ResolvedConfigurationIdentifier.java |    0
 .../api/internal/artifacts/ResolverResults.java    |    0
 .../artifacts/configurations/Configurations.java   |    0
 .../configurations/ConfigurationsProvider.java     |    0
 .../configurations/DefaultConfiguration.java       |  569 +++++++++
 .../DefaultConfigurationContainer.java             |  123 ++
 .../DetachedConfigurationsProvider.java            |    0
 .../artifacts/dsl/ForcedModuleNotationParser.java  |   97 ++
 .../artifacts/dsl/ParsedModuleStringNotation.java  |   68 ++
 .../ivyservice/ArtifactResolveResult.java          |    6 -
 .../ivyservice/BuildableArtifactResolveResult.java |    6 +-
 .../BuildableModuleVersionResolveResult.java       |    7 +-
 .../artifacts/ivyservice/CacheLockingManager.java  |   11 +
 .../DefaultBuildableArtifactResolveResult.java     |   11 +-
 ...DefaultBuildableModuleVersionResolveResult.java |   26 +-
 .../ivyservice/DefaultCacheLockingManager.java     |   12 +-
 .../DefaultDependencyResolveDetails.java           |   69 ++
 .../ivyservice/DefaultIvyDependencyPublisher.java  |    5 +-
 .../ivyservice/ErrorHandlingArtifactPublisher.java |   51 -
 .../ForcedModuleVersionIdResolveResult.java        |   47 -
 .../ivyservice/IvyBackedArtifactPublisher.java     |   34 +-
 .../ivyservice/IvyModuleDescriptorWriter.java      |   30 -
 .../ivyservice/IvyXmlModuleDescriptorWriter.java   |   14 +-
 .../ivyservice/ModuleDescriptorConverter.java      |   29 -
 .../ivyservice/ModuleVersionIdResolveResult.java   |   23 +-
 .../ivyservice/ModuleVersionNotFoundException.java |   16 +-
 .../ivyservice/ModuleVersionResolveException.java  |   51 +-
 .../ivyservice/ModuleVersionResolveResult.java     |    4 +-
 .../SubstitutedModuleVersionIdResolveResult.java   |   50 +
 .../VersionForcingDependencyToModuleResolver.java  |   62 +-
 .../DefaultCachedModuleResolution.java             |   20 +-
 .../dynamicversions/ModuleResolutionCache.java     |    8 +-
 .../ModuleResolutionCacheEntry.java                |   15 +-
 .../SingleFileBackedModuleResolutionCache.java     |   68 +-
 .../AbstractDependencyResolverAdapter.java         |   37 -
 .../ivyresolve/ArtifactOriginWithMetaData.java     |   49 -
 .../BuildableModuleVersionDescriptor.java          |   10 +-
 .../CacheLockingModuleVersionRepository.java       |    4 +-
 .../ivyresolve/CachingModuleVersionRepository.java |   82 +-
 .../ivyresolve/ChangingModuleDetector.java         |    2 +-
 .../DefaultBuildableModuleVersionDescriptor.java   |   19 +-
 .../ExternalResourceResolverAdapter.java           |   25 +-
 .../ivyresolve/IvyDependencyResolverAdapter.java   |   47 +-
 .../ivyresolve/LazyDependencyToModuleResolver.java |   22 +-
 .../ivyresolve/LocalModuleVersionRepository.java   |    4 +-
 .../ivyservice/ivyresolve/ModuleSource.java        |   24 +
 .../ivyresolve/ModuleVersionDescriptor.java        |    4 +-
 .../ivyresolve/ModuleVersionRepository.java        |    5 +-
 .../ivyservice/ivyresolve/ResolveIvyFactory.java   |    9 +-
 .../StartParameterResolutionOverride.java          |    4 +-
 .../ivyservice/ivyresolve/UserResolverChain.java   |   42 +-
 .../DownloadedIvyModuleDescriptorParser.java       |    8 +-
 .../parser/GradlePomModuleDescriptorBuilder.java   |    6 +-
 .../parser/GradlePomModuleDescriptorParser.java    |    3 +-
 .../parser/IvyXmlModuleDescriptorParser.java       | 1206 ++++++++++++++++++++
 .../parser/ModuleScopedParserSettings.java         |    4 +
 .../modulecache/DefaultCachedModuleDescriptor.java |   19 +-
 .../modulecache/DefaultModuleDescriptorCache.java  |  115 +-
 .../modulecache/ModuleDescriptorCache.java         |   13 +-
 .../modulecache/ModuleDescriptorCacheEntry.java    |   14 +-
 .../modulecache/ModuleDescriptorStore.java         |   36 +-
 .../PublishModuleDescriptorConverter.java          |  100 +-
 .../projectmodule/ProjectDependencyResolver.java   |    8 +-
 .../resolutionstrategy/DefaultCachePolicy.java     |  244 ++++
 .../DefaultResolutionStrategy.java                 |  129 +++
 .../LatestConflictResolution.java                  |   27 +
 .../ModuleForcingResolveRule.java                  |   57 +
 .../StrictConflictResolution.java                  |   27 +
 .../resolveengine/DefaultDependencyResolver.java   |    4 +-
 .../resolveengine/DependencyGraphBuilder.java      |   32 +-
 .../result/CachingDependencyResultFactory.java     |   18 +-
 .../result/InternalDependencyResult.java           |   10 +-
 .../result/ResolutionResultBuilder.java            |    7 +-
 .../result/VersionSelectionReasons.java            |   27 +-
 .../repositories/DefaultBaseRepositoryFactory.java |   10 +-
 .../repositories/DefaultIvyArtifactRepository.java |   24 +-
 .../IvyArtifactRepositoryInternal.java             |   26 -
 .../DownloadingRepositoryCacheManager.java         |    7 +-
 .../EnhancedArtifactDownloadReport.java            |   18 -
 .../LocalFileRepositoryCacheManager.java           |    4 +-
 .../layout/PatternRepositoryLayout.java            |   26 +-
 .../resolver/ExternalResourceResolver.java         |  469 ++++++--
 .../repositories/resolver/M2ResourcePattern.java   |   11 +
 .../repositories/resolver/MavenMetadataLoader.java |    2 +-
 .../repositories/resolver/MavenResolver.java       |  146 +--
 .../transport/RepositoryTransport.java             |    4 +-
 .../artifacts/result/AbstractDependencyResult.java |   45 +
 .../result/DefaultResolvedDependencyResult.java    |   26 +-
 .../result/DefaultResolvedModuleVersionResult.java |    4 +-
 .../result/DefaultUnresolvedDependencyResult.java  |   37 +-
 .../externalresource/cached/CachedArtifact.java    |   23 +
 .../cached/CachedArtifactIndex.java                |   63 +
 .../cached/CachedExternalResource.java             |   31 +-
 .../externalresource/cached/CachedItem.java        |   49 +
 .../cached/DefaultCachedArtifact.java              |   55 +
 .../cached/DefaultCachedExternalResourceIndex.java |   85 +-
 .../externalresource/ivy/AbstractCachedIndex.java  |  104 ++
 .../ArtifactAtRepositoryCachedArtifactIndex.java   |  103 ++
 ...actAtRepositoryCachedExternalResourceIndex.java |   30 -
 .../ivy/ArtifactAtRepositoryKey.java               |   23 +-
 .../ivy/LocallyAvailableResourceFinderFactory.java |   13 +-
 ...PatternBasedLocallyAvailableResourceFinder.java |   41 +-
 .../transport/file/FileResourceConnector.java      |    8 +-
 .../transport/file/FileTransport.java              |   12 +-
 .../transport/http/HttpClientHelper.java           |    9 +
 .../transport/http/HttpTransport.java              |   25 +-
 .../notations/DependencyStringNotationParser.java  |    4 +-
 .../DefaultDependencyManagementServicesTest.groovy |   40 +-
 .../artifacts/DefaultModuleIdentifierSpec.groovy   |   42 +
 .../DefaultModuleVersionIdentifierSpec.groovy      |   50 +
 .../ResolvedConfigurationIdentifierSpec.groovy     |   41 +
 .../internal/artifacts/ResolverResultsSpec.groovy  |    0
 .../configurations/ConfigurationsTest.java         |   38 +
 .../DefaultConfigurationContainerSpec.groovy       |  101 ++
 .../DefaultConfigurationContainerTest.groovy       |  120 ++
 .../configurations/DefaultConfigurationSpec.groovy |  304 +++++
 .../configurations/DefaultConfigurationTest.java   |  955 ++++++++++++++++
 .../dsl/ForcedModuleNotationParserSpec.groovy      |  126 ++
 ...efaultBuildableArtifactResolveResultTest.groovy |   21 -
 ...tBuildableModuleVersionResolveResultTest.groovy |   13 +-
 .../DefaultDependencyResolveDetailsSpec.groovy     |  121 ++
 .../ErrorHandlingArtifactPublisherTest.groovy      |   81 --
 .../ivyservice/IvyBackedArtifactPublisherTest.java |   18 +-
 .../IvyXmlModuleDescriptorWriterTest.groovy        |    6 +-
 .../ModuleVersionNotFoundExceptionTest.groovy      |   51 +
 .../ModuleVersionResolveExceptionTest.groovy       |   34 +-
 .../ivyservice/ResolvedArtifactFactoryTest.groovy  |    2 +-
 ...ionForcingDependencyToModuleResolverSpec.groovy |  104 ++
 ...ionForcingDependencyToModuleResolverTest.groovy |   65 --
 .../clientmodule/ClientModuleResolverTest.groovy   |    4 +-
 .../CachingModuleVersionRepositoryTest.groovy      |   27 +-
 ...aultBuildableModuleVersionDescriptorTest.groovy |   53 +-
 .../LazyDependencyToModuleResolverTest.groovy      |   44 +-
 .../ivyresolve/UserResolverChainTest.groovy        |  142 ++-
 .../DownloadedIvyModuleDescriptorParserTest.groovy |    8 +-
 .../GradlePomModuleDescriptorParserTest.groovy     |   30 +-
 .../parser/IvyXmlModuleDescriptorParserTest.groovy |  412 +++++++
 .../modulecache/ModuleDescriptorStoreTest.groovy   |   31 +-
 .../ProjectDependencyResolverTest.groovy           |   15 +-
 .../DefaultCachePolicySpec.groovy                  |  275 +++++
 .../DefaultResolutionStrategySpec.groovy           |  164 +++
 .../ModuleForcingResolveRuleSpec.groovy            |   92 ++
 .../DependencyGraphBuilderTest.groovy              |   58 +-
 .../CachingDependencyResultFactoryTest.groovy      |   13 +-
 .../result/ResolutionResultBuilderSpec.groovy      |   14 +-
 .../DefaultLocalMavenRepositoryLocatorTest.groovy  |    7 +-
 .../DefaultBaseRepositoryFactoryTest.groovy        |    4 +-
 .../DefaultIvyArtifactRepositoryTest.groovy        |  117 +-
 .../DownloadingRepositoryCacheManagerTest.groovy   |    4 +-
 .../resolver/ExternalResourceResolverTest.groovy   |  138 +++
 .../resolver/M2ResourcePatternTest.groovy          |    9 +
 .../DefaultArtifactResolutionCacheTest.groovy      |   10 +-
 .../CachedExternalResourceAdapterTest.groovy       |   12 +-
 ...ifactAtRepositoryCachedArtifactIndexTest.groovy |  132 +++
 .../DefaultLocallyAvailableResourceTest.groovy     |    6 +-
 ...zyLocallyAvailableResourceCandidatesTest.groovy |    8 +-
 .../transport/http/HttpClientHelperTest.groovy     |   32 +-
 .../ivyresolve/parser/test-bad-confs.xml           |   27 +
 .../ivyresolve/parser/test-cyclic-confs1.xml       |   28 +
 .../ivyresolve/parser/test-empty-dependencies.xml  |   28 +
 .../ivyservice/ivyresolve/parser/test-full.xml     |  108 ++
 .../result/ResolutionResultDataBuilder.groovy      |    6 +-
 subprojects/core/core.gradle                       |    6 +-
 .../ConfigurationOnDemandIntegrationTest.groovy    |  172 +++
 ...ependencyResolutionEventsIntegrationTest.groovy |   52 -
 .../api/dsl/DynamicObjectIntegrationTest.groovy    |  110 +-
 .../gradle/api/tasks/ArchiveIntegrationTest.groovy |    3 +-
 .../ArchiveTaskPermissionsIntegrationTest.groovy   |    3 +-
 .../api/tasks/CopyErrorIntegrationTest.groovy      |   10 +-
 .../tasks/CopyPermissionsIntegrationTest.groovy    |    7 +-
 .../api/tasks/CopyTaskIntegrationTest.groovy       |   10 +-
 ...tionTimeTaskConfigurationIntegrationTest.groovy |  164 +++
 .../api/tasks/FileTreeCopyIntegrationTest.groovy   |    4 +-
 .../internal/WorkerProcessIntegrationTest.java     |  386 +++++++
 .../ProjectBuilderIntegrationTest.groovy           |   53 +
 .../src/main/groovy/org/gradle/StartParameter.java |   44 +
 .../org/gradle/api/InvalidUserCodeException.java   |   36 +
 .../src/main/groovy/org/gradle/api/Project.java    |    9 +
 .../api/artifacts/DependencyResolveDetails.java    |   55 +
 .../org/gradle/api/artifacts/ModuleIdentifier.java |   39 +
 .../api/artifacts/ModuleVersionIdentifier.java     |   25 +-
 .../gradle/api/artifacts/ResolutionStrategy.java   |   52 +-
 .../api/artifacts/ResolvableDependencies.java      |    1 +
 .../artifacts/cache/ArtifactResolutionControl.java |    1 +
 .../api/artifacts/result/DependencyResult.java     |   12 +-
 .../result/ModuleVersionSelectionReason.java       |   15 +
 .../artifacts/result/ResolvedDependencyResult.java |   12 +-
 .../result/ResolvedModuleVersionResult.java        |   25 +-
 .../result/UnresolvedDependencyResult.java         |   20 +-
 .../gradle/api/component/SoftwareComponent.java    |   27 +
 .../api/component/SoftwareComponentContainer.java  |   27 +
 .../org/gradle/api/component/package-info.java     |   25 +
 .../main/groovy/org/gradle/api/file/FileTree.java  |    4 +-
 .../org/gradle/api/internal/AbstractTask.java      |   66 +-
 .../gradle/api/internal/ClosureBackedAction.java   |   31 +
 .../gradle/api/internal/ConfigureByMapAction.java  |   71 ++
 .../api/internal/LocationAwareException.java       |   28 +-
 .../gradle/api/internal/PropertiesTransformer.java |    9 -
 .../org/gradle/api/internal/TaskInternal.java      |    2 +
 .../org/gradle/api/internal/UserCodeAction.java    |   43 +
 .../org/gradle/api/internal/XmlTransformer.java    |  343 ------
 .../artifacts/ArtifactPublicationServices.java     |   11 +-
 .../api/internal/artifacts/ArtifactPublisher.java  |    5 +-
 .../artifacts/ArtifactPublisherFactory.java        |   25 -
 .../artifacts/DefaultArtifactPublisherFactory.java |   46 -
 .../DefaultArtifactRepositoryContainer.java        |   47 +-
 .../artifacts/DefaultModuleVersionIdentifier.java  |   85 --
 .../artifacts/DefaultModuleVersionSelector.java    |  108 --
 .../artifacts/DependencyResolutionServices.java    |    3 +-
 .../DependencyResolveDetailsInternal.java          |   29 +
 .../internal/artifacts/ProjectBackedModule.java    |   45 +
 .../configurations/DefaultConfiguration.java       |  568 ---------
 .../DefaultConfigurationContainer.java             |  122 --
 .../configurations/DefaultResolutionStrategy.java  |   90 --
 .../configurations/ForcedModuleNotationParser.java |   98 --
 .../configurations/ResolutionStrategyInternal.java |   13 +
 .../conflicts/LatestConflictResolution.java        |   27 -
 .../conflicts/StrictConflictResolution.java        |   27 -
 .../configurations/dynamicversion/CachePolicy.java |    2 +-
 .../dynamicversion/DefaultCachePolicy.java         |  214 ----
 .../dependencies/DefaultProjectDependency.java     |    3 +-
 .../artifacts/dsl/DefaultRepositoryFactory.java    |   98 --
 .../artifacts/dsl/DefaultRepositoryHandler.java    |   40 +-
 .../internal/artifacts/dsl/RepositoryFactory.java  |  111 --
 .../artifacts/dsl/RepositoryFactoryInternal.java   |   25 -
 .../dependencies/ParsedModuleStringNotation.java   |   68 --
 .../ivyservice/IvyModuleDescriptorWriter.java      |   30 +
 .../ivyservice/ModuleDescriptorConverter.java      |   29 +
 .../artifacts/publish/ArchivePublishArtifact.java  |    1 +
 .../result/ResolvedDependencyResultPrinter.java    |   39 -
 .../CacheBackedTaskHistoryRepository.java          |    2 +-
 .../internal/changedetection/CachingHasher.java    |   13 +-
 .../changedetection/InMemoryIndexedCache.java      |   22 +-
 .../DefaultSoftwareComponentContainer.java         |   28 +
 .../component/SoftwareComponentInternal.java       |   26 +
 .../api/internal/file/AbstractFileTreeElement.java |    5 +-
 .../api/internal/file/DefaultFileOperations.java   |    3 +-
 .../api/internal/file/DefaultFileVisitDetails.java |   35 +
 .../file/DefaultTemporaryFileProvider.java         |    4 +-
 .../file/collections/DirectoryFileTree.java        |   43 +-
 .../internal/file/collections/MinimalFileTree.java |    2 +-
 .../collections/SingleIncludePatternFileTree.java  |  129 +++
 .../api/internal/file/copy/CopySpecImpl.java       |    4 +-
 .../internal/file/copy/SyncCopySpecVisitor.java    |    2 +-
 .../api/internal/filestore/PathKeyFileStore.java   |   12 +-
 .../parsers/CharSequenceNotationParser.java        |   28 +
 .../api/internal/project/AbstractProject.java      |   21 +-
 .../project/GradleInternalServiceRegistry.java     |    3 +-
 .../api/internal/project/ProjectInternal.java      |    2 +
 .../project/ProjectInternalServiceRegistry.java    |   15 +-
 .../project/TaskInternalServiceRegistry.java       |   11 +-
 .../project/TopLevelBuildServiceRegistry.java      |   10 +-
 .../OutputDirectoryPropertyAnnotationHandler.java  |    6 +-
 .../OutputFilePropertyAnnotationHandler.java       |    5 +-
 .../api/internal/resource/ResourceException.java   |    2 +
 .../api/internal/tasks/DefaultTaskContainer.java   |   11 +
 .../api/internal/tasks/DefaultTaskInputs.java      |   12 +-
 .../api/internal/tasks/DefaultTaskOutputs.java     |   13 +-
 .../api/internal/tasks/TaskContainerInternal.java  |   11 +-
 .../api/internal/tasks/TaskStateInternal.java      |    3 +
 .../api/internal/tasks/TaskStatusNagger.java       |  102 ++
 .../execution/ExecuteActionsTaskExecuter.java      |    6 +-
 .../gradle/api/internal/xml/SimpleXmlWriter.java   |  426 +++++++
 .../gradle/api/internal/xml/XmlTransformer.java    |  346 ++++++
 .../ivy/internal/IvyNormalizedPublication.java     |   55 -
 .../api/publish/ivy/internal/IvyPublisher.java     |   33 -
 .../groovy/org/gradle/api/tasks/Directory.groovy   |    9 +-
 .../groovy/org/gradle/api/tasks/TaskContainer.java |   14 +-
 .../main/groovy/org/gradle/api/tasks/Upload.java   |   79 +-
 .../org/gradle/api/tasks/util/PatternSet.java      |   80 +-
 .../groovy/org/gradle/cache/DefaultSerializer.java |   55 -
 .../groovy/org/gradle/cache/PersistentCache.java   |   10 +
 .../gradle/cache/internal/DefaultCacheAccess.java  |    8 +-
 .../gradle/cache/internal/DefaultCacheFactory.java |    1 +
 .../cache/internal/DefaultCacheRepository.java     |    1 +
 .../cache/internal/DefaultFileLockManager.java     |    9 +-
 .../internal/DefaultPersistentDirectoryStore.java  |    6 +-
 .../DelegateOnDemandPersistentDirectoryCache.java  |    4 +
 .../configuration/DefaultBuildConfigurer.java      |   13 +-
 .../configuration/ImplicitTasksConfigurer.java     |   15 +-
 .../execution/OnlyWhenConfigureOnDemand.java       |   39 +
 .../gradle/execution/ProjectEvaluatingAction.java  |   47 +
 .../gradle/execution/ProjectFinderByTaskPath.java  |   71 ++
 .../gradle/execution/TaskPathProjectEvaluator.java |   48 +
 .../groovy/org/gradle/execution/TaskSelector.java  |   31 +-
 .../commandline/CommandLineTaskConfigurer.java     |   13 +-
 .../commandline/CommandLineTaskParser.java         |   14 -
 .../taskgraph/DefaultTaskGraphExecuter.java        |    7 +-
 .../internal/DefaultScriptCompilationHandler.java  |    2 +-
 .../initialization/CompositeInitScriptFinder.java  |    8 +-
 .../initialization/DefaultExceptionAnalyser.java   |    5 +-
 .../initialization/DefaultGradleLauncher.java      |   11 +-
 .../initialization/DirectoryInitScriptFinder.java  |    7 +-
 .../DistributionInitScriptFinder.java              |   17 +-
 .../gradle/initialization/InitScriptFinder.java    |    6 +-
 .../gradle/initialization/InitScriptHandler.java   |   15 +-
 .../initialization/ProvidedInitScriptFinder.java   |   37 -
 .../initialization/UserHomeInitScriptFinder.java   |   16 +-
 .../org/gradle/invocation/DefaultGradle.java       |   15 +-
 .../org/gradle/listener/ActionBroadcast.java       |    4 -
 .../org/gradle/listener/BroadcastDispatch.java     |   27 -
 .../ClosureBackedMethodInvocationDispatch.java     |   71 ++
 .../gradle/listener/DefaultListenerManager.java    |    7 +-
 .../org/gradle/listener/ListenerBroadcast.java     |    8 -
 .../org/gradle/logging/internal/OutputEvent.java   |    4 +-
 .../process/internal/DefaultWorkerProcess.java     |   53 +-
 .../internal/DefaultWorkerProcessFactory.java      |   14 +-
 .../internal/child/ActionExecutionWorker.java      |   50 +-
 .../internal/InMemoryCacheFactory.java             |   18 +-
 .../main/groovy/org/gradle/util/ConfigureUtil.java |   13 +-
 .../main/groovy/org/gradle/util/GFileUtils.java    |   13 +-
 .../main/groovy/org/gradle/util/GradleVersion.java |   10 -
 .../core/src/main/groovy/org/gradle/util/Jvm.java  |    4 -
 .../src/main/groovy/org/gradle/util/TextUtil.java  |    7 +-
 .../groovy/org/gradle/util/hash/HashValue.java     |   14 +-
 .../groovy/org/gradle/StartParameterTest.groovy    |   30 +-
 .../gradle/api/internal/AbstractTaskSpec.groovy    |   67 ++
 .../api/internal/ClosureBackedActionTest.groovy    |  103 ++
 .../api/internal/ConfigureByMapActionTest.groovy   |  103 ++
 .../org/gradle/api/internal/DefaultTaskTest.groovy |   58 +-
 .../api/internal/DocumentationRegistryTest.groovy  |    8 +-
 .../api/internal/LocationAwareExceptionTest.groovy |   29 +
 .../api/internal/PropertiesTransformerTest.groovy  |   14 +-
 .../gradle/api/internal/XmlTransformerTest.groovy  |  305 -----
 .../DefaultArtifactRepositoryContainerTest.groovy  |   26 +-
 .../DefaultModuleVersionIdentifierTest.groovy      |   42 -
 .../artifacts/ProjectBackedModuleTest.groovy       |   45 +
 .../ResolvedConfigurationIdentifierTest.groovy     |   41 -
 .../configurations/ConfigurationsTest.java         |   37 -
 .../DefaultConfigurationContainerSpec.groovy       |  101 --
 .../DefaultConfigurationContainerTest.groovy       |  117 --
 .../configurations/DefaultConfigurationSpec.groovy |  291 -----
 .../configurations/DefaultConfigurationTest.java   |  954 ----------------
 .../DefaultResolutionStrategyTest.groovy           |   62 -
 .../ForcedModuleNotationParserTest.groovy          |  126 --
 .../dynamicversion/DefaultCachePolicySpec.groovy   |  250 ----
 .../dependencies/DefaultProjectDependencyTest.java |    1 +
 .../dsl/DefaultRepositoryFactoryTest.groovy        |  137 ---
 .../dsl/DefaultRepositoryHandlerTest.groovy        |   38 +-
 .../publish/ArchivePublishArtifactTest.groovy      |   83 ++
 .../publish/ArchivePublishArtifactTest.java        |   80 --
 .../changedetection/CachingHasherTest.java         |    4 +-
 .../DefaultFileSnapshotterTest.groovy              |   24 +-
 .../DefaultTaskArtifactStateRepositoryTest.java    |    6 +-
 .../classpath/DefaultModuleRegistryTest.groovy     |    6 +-
 .../api/internal/classpath/ManifestUtilTest.groovy |   11 +-
 .../internal/file/AbstractFileCollectionTest.java  |   10 +-
 .../internal/file/AbstractFileTreeElementTest.java |    8 +-
 .../internal/file/BaseDirFileResolverSpec.groovy   |   51 +-
 .../internal/file/BaseDirFileResolverTest.groovy   |    6 +-
 .../internal/file/DefaultFileOperationsTest.groovy |   20 +-
 .../file/DefaultFileTreeElementTest.groovy         |    7 +-
 .../file/DefaultSourceDirectorySetTest.groovy      |    9 +-
 .../file/DefaultTemporaryFileProviderTest.groovy   |   11 +-
 .../file/FileOrUriNotationParserTest.groovy        |    4 +-
 .../file/archive/TarCopySpecVisitorTest.java       |   22 +-
 .../api/internal/file/archive/TarFileTreeTest.java |   26 +-
 .../file/archive/ZipCopySpecVisitorTest.java       |   19 +-
 .../api/internal/file/archive/ZipFileTreeTest.java |   35 +-
 .../DefaultConfigurableFileCollectionTest.java     |    4 +-
 .../DefaultConfigurableFileTreeTest.groovy         |    7 +-
 .../file/collections/DirectoryFileTreeTest.java    |   20 +-
 .../internal/file/collections/MapFileTreeTest.java |   19 +-
 .../SingleIncludePatternFileTreeSpec.groovy        |  315 +++++
 .../api/internal/file/copy/CopySpecImplTest.groovy |    9 +-
 .../internal/file/copy/DeleteActionImplTest.groovy |   19 +-
 .../file/copy/FileCopySpecVisitorTest.java         |    9 +-
 .../file/copy/MappingCopySpecVisitorTest.java      |   10 +-
 .../file/copy/SyncCopySpecVisitorTest.java         |    8 +-
 .../internal/filestore/PathKeyFileStoreTest.groovy |    6 +-
 .../PathNormalisingKeyFileStoreTest.groovy         |    8 +-
 .../filestore/UniquePathKeyFileStoreTest.groovy    |    4 +-
 .../parsers/CharSequenceNotationParserTest.groovy  |   47 +
 .../parsers/ClosureToSpecNotationParserSpec.groovy |   40 -
 .../parsers/ClosureToSpecNotationParserTest.groovy |   40 +
 .../plugins/DefaultPluginRegistryTest.groovy       |    4 +-
 .../api/internal/project/DefaultProjectTest.groovy |    5 +
 .../api/internal/project/ProjectFactoryTest.java   |    6 +-
 .../ProjectInternalServiceRegistryTest.java        |    9 +-
 .../project/TaskInternalServiceRegistryTest.java   |   14 +-
 .../TopLevelBuildServiceRegistryTest.groovy        |   10 +-
 .../AnnotationProcessingTaskFactoryTest.java       |   10 +-
 .../api/internal/resource/UriResourceTest.groovy   |    9 +-
 .../internal/tasks/DefaultTaskContainerTest.java   |   41 +-
 .../internal/tasks/DefaultTaskInputsTest.groovy    |   62 +-
 .../internal/tasks/DefaultTaskOutputsTest.groovy   |   36 +-
 .../internal/tasks/TaskStateInternalTest.groovy    |    5 +
 .../execution/ExecuteActionsTaskExecuterTest.java  |   50 +-
 .../api/internal/xml/SimpleXmlWriterSpec.groovy    |  417 +++++++
 .../api/internal/xml/XmlTransformerTest.groovy     |  306 +++++
 .../org/gradle/api/tasks/DirectoryTest.groovy      |    3 +-
 .../groovy/org/gradle/api/tasks/UploadTest.groovy  |   32 +
 .../groovy/org/gradle/api/tasks/UploadTest.java    |  135 ---
 .../org/gradle/api/tasks/ant/AntTargetTest.java    |   16 +-
 .../gradle/api/tasks/util/PatternSetTest.groovy    |  191 ++--
 .../org/gradle/cache/DefaultSerializerTest.groovy  |   36 -
 .../cache/internal/DefaultCacheAccessTest.groovy   |    4 +-
 .../cache/internal/DefaultCacheFactoryTest.groovy  |  124 +-
 .../internal/DefaultCacheRepositoryTest.groovy     |   12 +-
 .../internal/DefaultFileLockManagerTest.groovy     |    6 +-
 .../DefaultPersistentDirectoryCacheSpec.groovy     |   14 +-
 .../DefaultPersistentDirectoryCacheTest.java       |   18 +-
 .../DefaultPersistentDirectoryStoreTest.groovy     |    5 +-
 ...gateOnDemandPersistentDirectoryCacheSpec.groovy |   10 +-
 .../cache/internal/OnDemandFileAccessTest.groovy   |    4 +-
 .../cache/internal/SimpleStateCacheTest.groovy     |    8 +-
 .../btree/BTreePersistentIndexedCacheTest.java     |    8 +-
 .../DefaultBuildConfigurerTest.groovy              |   26 +-
 .../ImplicitTasksConfigurerTest.groovy             |    8 +
 .../execution/OnlyWhenConfigureOnDemandTest.groovy |   63 +
 .../execution/ProjectEvaluatingActionTest.groovy   |   68 ++
 .../execution/ProjectFinderByTaskPathTest.groovy   |   73 ++
 ...kNameResolvingBuildConfigurationActionTest.java |    3 +-
 .../execution/TaskPathProjectEvaluatorTest.groovy  |   57 +
 .../CommandLineTaskConfigurerSpec.groovy           |   10 +
 .../commandline/CommandLineTaskParserSpec.groovy   |   10 -
 .../taskgraph/DefaultTaskGraphExecuterTest.java    |    4 +-
 .../gradle/groovy/scripts/UriScriptSourceTest.java |   12 +-
 .../DefaultScriptCompilationHandlerTest.java       |   15 +-
 .../initialization/BuildFileProjectSpecTest.java   |   11 +-
 .../initialization/BuildSourceBuilderTest.groovy   |    6 +-
 .../CompositeInitScriptFinderTest.groovy           |    8 +-
 .../DefaultCommandLineConverterTest.java           |    6 +-
 .../initialization/DefaultGradleLauncherTest.java  |    4 +-
 .../DefaultGradlePropertiesLoaderTest.java         |    9 +-
 .../DistributionInitScriptFinderTest.groovy        |   34 +-
 .../initialization/InitScriptHandlerTest.groovy    |   33 +-
 .../InstantiatingBuildLoaderTest.groovy            |    7 +-
 .../ProjectDirectoryProjectSpecTest.java           |   11 +-
 .../ProjectPropertySettingBuildLoaderTest.groovy   |    4 +-
 .../ProvidedInitScriptFinderTest.java              |   73 --
 .../UserHomeInitScriptFinderTest.java              |   40 +-
 .../layout/BuildLayoutFactoryTest.groovy           |   20 +-
 .../org/gradle/invocation/DefaultGradleTest.java   |    5 +-
 .../org/gradle/listener/ActionBroadcastTest.groovy |   14 +-
 .../org/gradle/listener/ListenerBroadcastTest.java |   13 +-
 .../process/internal/DefaultExecHandleSpec.groovy  |    9 +-
 .../internal/DefaultWorkerProcessFactoryTest.java  |  112 --
 .../internal/DefaultWorkerProcessTest.groovy       |   45 +-
 .../internal/child/ActionExecutionWorkerTest.java  |    4 +
 .../child/ImplementationClassLoaderWorkerTest.java |    7 +-
 .../WorkerProcessClassPathProviderTest.groovy      |   17 +-
 .../gradle/reporting/HtmlReportRendererTest.groovy |   10 +-
 .../gradle/reporting/TextReportRendererTest.groovy |    6 +-
 .../gradle/testfixtures/ProjectBuilderTest.groovy  |   16 +-
 .../org/gradle/util/ConfigureUtilTest.groovy       |  129 ++-
 .../groovy/org/gradle/util/GFileUtilsTest.groovy   |    9 +-
 .../org/gradle/util/GradleVersionTest.groovy       |   13 -
 .../groovy/org/gradle/util/MatchersTest.groovy     |   43 +
 .../internal/{ => xml}/xml-transformer-test.dtd    |    0
 .../gradle/api/tasks/AbstractSpockTaskTest.groovy  |    4 +-
 .../org/gradle/api/tasks/AbstractTaskTest.java     |   74 +-
 .../tasks/bundling/AbstractArchiveTaskTest.groovy  |    6 +-
 .../gradle/test/fixtures/ConcurrentTestUtil.groovy |  772 +++++++++++++
 .../tests/fixtures/ConcurrentTestUtil.groovy       |  771 -------------
 .../org/gradle/util/ConcurrentSpecification.groovy |    2 +-
 .../groovy/org/gradle/util/HelperUtil.groovy       |   55 +-
 .../groovy/org/gradle/util/Matchers.java           |   30 +-
 .../cpp/AbstractBinariesIntegrationSpec.groovy     |    2 +-
 .../gradle/plugins/binaries/BinariesPlugin.java    |    9 +-
 .../cpp/cdt/model/CprojectDescriptor.groovy        |    2 +-
 .../plugins/cpp/cdt/model/ProjectDescriptor.groovy |    2 +-
 .../cpp/cdt/tasks/GenerateMetadataFileTask.groovy  |    3 +-
 .../compiler/internal/CommandLineCppCompiler.java  |    3 +-
 .../gradle/plugins/cpp/gpp/GppCompileSpec.groovy   |    4 +-
 .../plugins/cpp/gpp/GppCompilerPlugin.groovy       |   17 +-
 .../cpp/msvcpp/MicrosoftVisualCppPlugin.groovy     |   18 +-
 .../cpp/cdt/model/ProjectDescriptorSpec.groovy     |    4 +-
 subprojects/diagnostics/diagnostics.gradle         |    2 +-
 ...pendencyInsightReportTaskIntegrationTest.groovy |  277 ++++-
 .../DependencyReportTaskIntegrationTest.groovy     |  192 +++-
 .../ResolutionResultApiIntegrationTest.groovy      |   79 ++
 .../TaskReportTaskIntegrationTest.groovy           |   62 +
 .../diagnostics/DependencyInsightReportTask.groovy |   28 +-
 .../tasks/diagnostics/DependencyReportTask.java    |   23 +-
 .../api/tasks/diagnostics/ReportException.java     |   29 -
 .../api/tasks/diagnostics/TaskReportTask.java      |   12 +-
 .../internal/DependencyReportRenderer.java         |    2 +-
 .../tasks/diagnostics/internal/ReportRenderer.java |    2 +-
 .../AsciiDependencyReportRenderer.java             |    5 -
 .../internal/graph/DependencyGraphRenderer.groovy  |   35 +-
 .../diagnostics/internal/graph/NodeRenderer.groovy |    3 +-
 .../internal/graph/SimpleNodeRenderer.java         |   13 +-
 .../nodes/AbstractRenderableDependencyResult.java  |   74 +-
 .../nodes/AbstractRenderableModuleResult.java      |    4 +
 .../internal/graph/nodes/DependencyEdge.java       |   37 +
 .../nodes/InvertedRenderableDependencyResult.java  |   46 -
 .../nodes/InvertedRenderableModuleResult.java      |   18 +-
 .../internal/graph/nodes/RenderableDependency.java |    3 +-
 .../graph/nodes/RenderableDependencyResult.java    |   28 +-
 .../graph/nodes/RenderableModuleResult.java        |    8 +-
 .../RenderableUnresolvedDependencyResult.java      |   55 +
 .../internal/graph/nodes/RequestedVersion.java     |   71 ++
 .../graph/nodes/ResolvedDependencyEdge.java        |   57 +
 .../internal/graph/nodes/SimpleDependency.java     |   10 +-
 .../graph/nodes/UnresolvedDependencyEdge.java      |   61 +
 .../insight/DependencyInsightReporter.groovy       |   47 +-
 .../internal/insight/DependencyResultSorter.java   |   90 ++
 .../insight/ResolvedDependencyResultSorter.java    |   76 --
 .../tasks/diagnostics/AbstractReportTaskTest.java  |    6 +-
 .../DependencyInsightReportTaskSpec.groovy         |   12 +-
 .../diagnostics/DependencyReportTaskTest.groovy    |   42 +-
 .../api/tasks/diagnostics/TaskReportTaskTest.java  |    4 +
 .../internal/TextReportRendererTest.groovy         |    6 +-
 .../AsciiDependencyReportRendererTest.groovy       |    2 +-
 .../AbstractRenderableDependencyResultSpec.groovy  |   36 +-
 .../InvertedRenderableDependencyResultTest.groovy  |   72 --
 .../nodes/RenderableDependencyResultTest.groovy    |   53 +
 ...RenderableUnresolvedDependencyResultTest.groovy |   48 +
 .../insight/DependencyResultSorterSpec.groovy      |   86 ++
 .../ResolvedDependencyResultSorterSpec.groovy      |   79 --
 subprojects/distributions/distributions.gradle     |    3 +-
 .../gradle/AllDistributionIntegrationSpec.groovy   |    9 +-
 .../gradle/BinDistributionIntegrationSpec.groovy   |    9 +-
 .../org/gradle/DistributionIntegrationSpec.groovy  |   47 +-
 .../gradle/SrcDistributionIntegrationSpec.groovy   |    9 +-
 subprojects/docs/docs.gradle                       |   56 +-
 subprojects/docs/release-notes-transform.gradle    |    4 +-
 subprojects/docs/src/docs/dsl/dsl.xml              |   28 +-
 ...org.gradle.api.artifacts.ResolutionStrategy.xml |    3 +
 .../docs/dsl/org.gradle.api.invocation.Gradle.xml  |    3 +
 ...rg.gradle.api.plugins.DistributionExtension.xml |   24 +
 .../org.gradle.api.plugins.quality.Checkstyle.xml  |    4 +
 ...dle.api.plugins.quality.CheckstyleExtension.xml |    5 +
 ....gradle.api.publish.ivy.IvyModuleDescriptor.xml |    5 -
 ...api.publish.ivy.tasks.GenerateIvyDescriptor.xml |   24 +
 ...pi.publish.ivy.tasks.PublishToIvyRepository.xml |   24 +
 .../dsl/org.gradle.api.publish.maven.MavenPom.xml  |   25 +
 ...g.gradle.api.publish.maven.MavenPublication.xml |   28 +
 ...ublish.maven.tasks.PublishToMavenRepository.xml |   24 +
 .../docs/dsl/org.gradle.api.tasks.testing.Test.xml |    6 +-
 .../org.gradle.api.tasks.testing.TestReport.xml    |   31 +
 subprojects/docs/src/docs/dsl/plugins.xml          |    5 +-
 .../docs/src/docs/release/content/script.js        |  107 +-
 .../docs/src/docs/release/notes-template.md        |   63 +
 subprojects/docs/src/docs/release/notes.md         |  704 ++++++++----
 .../docs/src/docs/userguide/antlrPlugin.xml        |   52 +-
 .../docs/src/docs/userguide/applicationPlugin.xml  |   10 +
 .../docs/src/docs/userguide/artifactMngmt.xml      |    9 +
 .../docs/src/docs/userguide/buildLifecycle.xml     |    6 +-
 .../src/docs/userguide/commandLineTutorial.xml     |    9 +-
 subprojects/docs/src/docs/userguide/depMngmt.xml   |   86 ++
 .../docs/src/docs/userguide/featureLifecycle.xml   |    4 +-
 .../docs/src/docs/userguide/groovyPlugin.xml       |  134 ++-
 .../userguide/javaLibraryDistributionPlugin.xml    |   75 ++
 subprojects/docs/src/docs/userguide/javaPlugin.xml |   33 +-
 .../docs/src/docs/userguide/mavenPlugin.xml        |   14 +-
 .../docs/src/docs/userguide/multiproject.xml       |   44 +
 .../docs/src/docs/userguide/publishingIvy.xml      |   89 +-
 .../docs/src/docs/userguide/publishingMaven.xml    |  160 +++
 .../docs/src/docs/userguide/scalaPlugin.xml        |   65 +-
 .../docs/src/docs/userguide/standardPlugins.xml    |  347 ++++--
 subprojects/docs/src/docs/userguide/userguide.xml  |    2 +
 subprojects/docs/src/samples/antlr/build.gradle    |    2 +-
 .../samples/clientModuleDependencies/build.gradle  |    2 +-
 .../docs/src/samples/codeQuality/build.gradle      |    2 +-
 .../src/samples/customPlugin/plugin/build.gradle   |    2 +-
 .../samples/groovy/customizedLayout/build.gradle   |   15 +-
 .../samples/groovy/mixedJavaAndGroovy/build.gradle |    4 +-
 .../groovy/multiproject/buildSrc/build.gradle      |    2 +-
 .../multiproject/groovycDetector/build.gradle      |    2 +-
 .../java/org/gradle/test/DetectorTransform.java    |    3 +-
 .../groovy/multiproject/testproject/build.gradle   |    4 +-
 .../src/test/groovy/org/gradle/VersionTest.groovy  |    4 +-
 .../src/samples/groovy/quickstart/build.gradle     |    4 +-
 .../src/test/groovy/org/gradle/PersonTest.groovy   |    2 +-
 .../docs/src/samples/ivypublish-new/build.gradle   |   13 +-
 .../docs/src/samples/ivypublish-new/output-ivy.xml |    2 -
 .../docs/src/samples/ivypublish/build.gradle       |    4 +-
 .../docs/src/samples/java/base/test/build.gradle   |    2 +-
 .../src/samples/java/customizedLayout/build.gradle |    2 +-
 .../src/samples/java/multiproject/build.gradle     |    2 +-
 .../java/multiproject/buildSrc/build.gradle        |    2 +-
 .../docs/src/samples/java/onlyif/build.gradle      |    2 +-
 .../samples/java/withIntegrationTests/build.gradle |    4 +-
 .../src/samples/maven/publish-new/build.gradle     |   39 +
 .../publish-new/src/main/java/org/MyClass.java     |    5 +
 .../samples/scala/customizedLayout/build.gradle    |   15 +-
 .../docs/src/samples/scala/fsc/build.gradle        |    8 +-
 .../samples/scala/mixedJavaAndScala/build.gradle   |    3 +-
 .../docs/src/samples/scala/quickstart/build.gradle |   10 +-
 .../docs/src/samples/scala/zinc/build.gradle       |    8 +-
 .../src/samples/testing/testReport/build.gradle    |   26 +
 .../src/test/java/org/gradle/sample/CoreTest.java  |   11 +
 .../docs/src/samples/testing/testReport/readme.xml |    5 +
 .../src/samples/testing/testReport/settings.gradle |    2 +
 .../src/test/java/org/gradle/sample/UtilTest.java  |   11 +
 .../testng/java-jdk14-passing/build.gradle         |    0
 .../src/main/java/org/gradle/Ok.java               |    0
 .../src/test/java/org/gradle/OkTest.java           |    0
 .../testing/testng/java-jdk15-passing/build.gradle |   17 +
 .../src/main/java/org/gradle/Ok.java               |    0
 .../src/test/java/org/gradle/AbstractTest.java     |    0
 .../src/test/java/org/gradle/ConcreteTest.java     |    0
 .../src/test/java/org/gradle/OkTest.java           |    0
 .../src/test/java/org/gradle/SuiteCleanup.java     |    0
 .../src/test/java/org/gradle/SuiteSetup.java       |    0
 .../src/test/java/org/gradle/TestCleanup.java      |    0
 .../src/test/java/org/gradle/TestSetup.java        |    0
 .../testng/suitexmlbuilder/build.gradle            |    0
 .../src/main/java/org/gradle/testng/User.java      |    0
 .../src/main/java/org/gradle/testng/UserImpl.java  |    0
 .../test/java/org/gradle/testng/UserImplTest.java  |    0
 .../samples/testng/java-jdk15-passing/build.gradle |   15 -
 .../src/samples/toolingApi/eclipse/build.gradle    |    2 +-
 .../docs/src/samples/toolingApi/idea/build.gradle  |    2 +-
 .../docs/src/samples/toolingApi/model/build.gradle |    2 +-
 .../src/samples/toolingApi/runBuild/build.gradle   |    2 +-
 .../artifacts/defineRepository/build.gradle        |   13 +
 .../artifacts/externalDependencies/build.gradle    |    8 +-
 .../samples/userguide/artifacts/maven/build.gradle |    2 +-
 .../artifacts/resolutionStrategy/build.gradle      |   38 +-
 .../userguide/groovy/groovyDependency/build.gradle |   23 +
 .../samples/userguide/java/sourceSets/build.gradle |    2 +-
 .../userguide/javaLibraryDistribution/build.gradle |   28 +
 .../userguide/javaLibraryDistribution/readme.xml   |    3 +
 .../multiproject/dependencies/java/build.gradle    |    2 +-
 .../dependencies/javaWithCustomConf/build.gradle   |    2 +-
 .../userguide/scala/scalaDependency/build.gradle   |   11 +
 .../samples/userguide/tutorial/groovy/build.gradle |    4 +-
 .../userguide/tutorial/projectReports/build.gradle |    4 +-
 .../userguideOutput/dependencyInsightReport.out    |    2 +-
 .../userguideOutput/dependencyListReport.out       |   13 +-
 .../dependencyListReportFiltered.out               |    8 +
 .../publishingIvyPublishLifecycle.out              |   16 +
 .../userguideOutput/publishingIvyPublishSingle.out |   15 +
 .../publishingMavenPublishLocal.out                |   10 +
 .../publishingMavenPublishMinimal.out              |   10 +
 .../samples/webApplication/customised/build.gradle |    2 +-
 .../releasenotes/FunctionalReleaseNotesTest.groovy |  116 ++
 .../docs/releasenotes/ReleaseNotesPage.groovy      |   34 +
 .../releasenotes/ReleaseNotesTestContext.groovy    |   37 +
 .../releasenotes/StaticReleaseNotesTest.groovy     |   90 ++
 .../groovy/org/gradle/plugins/ear/EarPlugin.java   |   18 +-
 .../internal/DefaultDeploymentDescriptor.groovy    |    2 +-
 .../org/gradle/plugins/ear/EarPluginTest.groovy    |   46 +-
 .../plugins/ide/AbstractIdeIntegrationTest.groovy  |    4 +-
 .../eclipse/AbstractEclipseIntegrationTest.groovy  |    5 +-
 .../ide/eclipse/EclipseClasspathFixture.groovy     |    5 +-
 .../eclipse/EclipseClasspathIntegrationTest.groovy |    2 -
 ...ClasspathRemoteResolutionIntegrationTest.groovy |   25 +-
 .../EclipseClasspathResolveIntegrationTest.groovy  |  108 ++
 .../ide/eclipse/EclipseIntegrationTest.groovy      |    2 +-
 .../ide/eclipse/EclipseWtpIntegrationTest.groovy   |   49 +-
 .../plugins/ide/idea/IdeaIntegrationTest.groovy    |   13 +-
 .../plugins/ide/api/FileContentMerger.groovy       |    5 +-
 .../ide/api/PropertiesFileContentMerger.groovy     |    3 +-
 .../plugins/ide/api/XmlFileContentMerger.groovy    |    2 +-
 .../gradle/plugins/ide/api/XmlGeneratorTask.java   |    2 +-
 .../plugins/ide/eclipse/model/Classpath.groovy     |    2 +-
 .../plugins/ide/eclipse/model/Project.groovy       |    2 +-
 .../plugins/ide/eclipse/model/WtpComponent.groovy  |    8 +-
 .../plugins/ide/eclipse/model/WtpFacet.groovy      |    2 +-
 .../plugins/ide/idea/model/IdeaModuleIml.groovy    |    2 +-
 .../gradle/plugins/ide/idea/model/Module.groovy    |    2 +-
 .../gradle/plugins/ide/idea/model/Project.groovy   |    2 +-
 .../gradle/plugins/ide/idea/model/Workspace.groovy |    2 +-
 ...PropertiesPersistableConfigurationObject.groovy |    4 +-
 .../XmlPersistableConfigurationObject.groovy       |    2 +-
 .../plugins/ide/eclipse/model/ClasspathTest.groovy |   11 +-
 .../plugins/ide/eclipse/model/ProjectTest.groovy   |    6 +-
 .../ide/eclipse/model/WtpComponentTest.groovy      |   21 +-
 .../plugins/ide/eclipse/model/WtpFacetTest.groovy  |    9 +-
 .../model/internal/FileReferenceFactoryTest.groovy |    6 +-
 .../plugins/ide/idea/model/ModuleTest.groovy       |    2 +-
 .../plugins/ide/idea/model/PathFactoryTest.groovy  |   36 +-
 .../plugins/ide/idea/model/ProjectTest.groovy      |    2 +-
 .../plugins/ide/internal/GeneratorTaskTest.groovy  |    4 +-
 ...ertiesPersistableConfigurationObjectTest.groovy |    5 +-
 .../XmlPersistableConfigurationObjectTest.groovy   |    6 +-
 ...kCommandLineConfigurationIntegrationSpec.groovy |    2 +-
 .../integtests/AntProjectIntegrationTest.groovy    |    9 +-
 .../integtests/ApplicationIntegrationTest.groovy   |   82 +-
 .../BroadcastMessagingIntegrationTest.groovy       |  338 ------
 .../BuildAggregationIntegrationTest.groovy         |   27 +-
 .../BuildScriptClasspathIntegrationTest.java       |    4 +-
 .../BuildScriptErrorIntegrationTest.java           |    4 +-
 .../BuildScriptExecutionIntegrationTest.groovy     |   12 +-
 .../BuildSourceBuilderIntegrationTest.groovy       |    2 +-
 .../integtests/CacheProjectIntegrationTest.groovy  |   10 +-
 .../integtests/CharacterEncodingIntegTest.groovy   |    7 +-
 .../integtests/CommandLineIntegrationTest.groovy   |   51 +-
 .../integtests/CustomPluginIntegrationTest.groovy  |    6 +-
 .../ExternalScriptErrorIntegrationTest.groovy      |    9 +-
 .../ExternalScriptExecutionIntegrationTest.groovy  |    6 +-
 .../IncrementalBuildIntegrationTest.groovy         |    5 +-
 ...ementalGroovyProjectBuildIntegrationTest.groovy |   18 +-
 ...crementalJavaProjectBuildIntegrationTest.groovy |   32 +-
 .../IncrementalTestIntegrationTest.groovy          |   38 +-
 .../integtests/InitScriptErrorIntegrationTest.java |    4 +-
 .../InitScriptExecutionIntegrationTest.groovy      |   22 +-
 .../integtests/JavaProjectIntegrationTest.groovy   |    4 +-
 .../integtests/MavenProjectIntegrationTest.groovy  |   12 +-
 .../MultiProjectDependencyIntegrationTest.groovy   |    8 +-
 .../OsgiProjectSampleIntegrationTest.groovy        |   23 +-
 .../integtests/ProjectLayoutIntegrationTest.groovy |   73 +-
 .../integtests/ProjectLoadingIntegrationTest.java  |   20 +-
 .../SettingsScriptErrorIntegrationTest.java        |    4 +-
 .../SettingsScriptExecutionIntegrationTest.groovy  |   14 +-
 .../gradle/integtests/StdioIntegrationTest.groovy  |    4 +-
 .../TaskErrorExecutionIntegrationTest.groovy       |    4 +-
 .../integtests/TaskExecutionIntegrationTest.java   |    2 +-
 .../UnicastMessagingIntegrationTest.groovy         |  268 -----
 .../integtests/WaterProjectIntegrationTest.groovy  |   13 +-
 .../integtests/WebProjectIntegrationTest.java      |    2 +-
 .../integtests/WorkerProcessIntegrationTest.java   |  382 -------
 .../BuildEnvironmentIntegrationTest.groovy         |   18 +-
 .../DistroTempDirIsUniquePerTestSpec.groovy        |   55 -
 .../integtests/fixture/M2Installation.groovy       |   55 -
 .../fixture/TempDirIsUniquePerTestSpec.groovy      |   12 +-
 .../logging/LoggingIntegrationTest.groovy          |   17 +-
 .../ivy/IvyHttpPublishIntegrationTest.groovy       |   10 +-
 .../ivy/IvySFtpPublishIntegrationTest.groovy       |    6 +-
 .../ivy/SamplesIvyPublishIntegrationTest.groovy    |   10 +-
 .../MavenJavaProjectPublishIntegrationTest.groovy  |    4 +-
 .../MavenMultiProjectPublishIntegrationTest.groovy |    8 +-
 .../maven/MavenPomGenerationIntegrationTest.groovy |    4 +-
 ...MavenPublishRespectsPomConfigurationTest.groovy |    2 +-
 ...SamplesMavenPomGenerationIntegrationTest.groovy |    4 +-
 .../SamplesMavenQuickstartIntegrationTest.groovy   |    4 +-
 .../AbstractDependencyResolutionTest.groovy        |   67 --
 .../ArtifactDependenciesIntegrationTest.groovy     |  659 -----------
 .../ArtifactOnlyResolutionIntegrationTest.groovy   |   90 --
 .../resolve/CacheResolveIntegrationTest.groovy     |  167 ---
 ...ModuleDependenciesResolveIntegrationTest.groovy |   83 --
 .../DependenciesResolveIntegrationTest.java        |   42 -
 .../DependencyNotationIntegrationSpec.groovy       |  153 ---
 .../ProjectDependencyResolveIntegrationTest.groovy |  298 -----
 .../VersionConflictResolutionIntegTest.groovy      |  815 -------------
 ...AliasedArtifactResolutionIntegrationTest.groovy |  201 ----
 .../CacheReuseCrossVersionIntegrationTest.groovy   |  170 ---
 .../M3CacheReuseCrossVersionIntegrationTest.groovy |   75 --
 .../MavenM2CacheReuseIntegrationTest.groovy        |   55 -
 .../ResolutionOverrideIntegrationTest.groovy       |  211 ----
 ...achedDependencyResolutionIntegrationTest.groovy |  221 ----
 .../CachedMissingModulesIntegrationTest.groovy     |  260 -----
 .../custom/IvySFtpResolverIntegrationTest.groovy   |   82 --
 .../custom/IvyUrlResolverIntegrationTest.groovy    |  125 --
 .../AbstractHttpsRepoResolveIntegrationTest.groovy |  129 ---
 ...ationDependencyResolutionIntegrationTest.groovy |  221 ----
 ...odingDependencyResolutionIntegrationTest.groovy |   49 -
 .../http/HttpProxyResolveIntegrationTest.groovy    |  154 ---
 .../http/HttpRedirectResolveIntegrationTest.groovy |   87 --
 .../IvyBrokenRemoteResolveIntegrationTest.groovy   |  196 ----
 ...angingModuleRemoteResolveIntegrationTest.groovy |  425 -------
 ...amicRevisionRemoteResolveIntegrationTest.groovy |  739 ------------
 ...IvyDynamicRevisionResolveIntegrationTest.groovy |  334 ------
 .../ivy/IvyFileRepoResolveIntegrationTest.groovy   |  125 --
 .../ivy/IvyHttpRepoResolveIntegrationTest.groovy   |  215 ----
 .../resolve/ivy/IvyResolveIntegrationTest.groovy   |  144 ---
 .../maven/BadPomFileResolveIntegrationTest.groovy  |   46 -
 .../MavenDependencyResolveIntegrationTest.groovy   |  175 ---
 .../MavenDynamicResolveIntegrationTest.groovy      |  166 ---
 .../MavenFileRepoResolveIntegrationTest.groovy     |  136 ---
 .../MavenHttpRepoResolveIntegrationTest.groovy     |  324 ------
 .../MavenLocalRepoResolveIntegrationTest.groovy    |  168 ---
 .../MavenParentPomResolveIntegrationTest.groovy    |  124 --
 .../MavenPomPackagingResolveIntegrationTest.groovy |  247 ----
 .../MavenSnapshotResolveIntegrationTest.groovy     |  565 ---------
 .../samples/SamplesAnnounceIntegrationTest.groovy  |   11 +-
 .../samples/SamplesAntlrIntegrationTest.groovy     |   14 +-
 .../SamplesApplicationIntegrationTest.groovy       |   12 +-
 ...sClientModuleDependenciesIntegrationTest.groovy |   41 +
 .../SamplesCodeQualityIntegrationTest.groovy       |   12 +-
 ...amplesCustomBuildLanguageIntegrationTest.groovy |   14 +-
 .../SamplesCustomPluginIntegrationTest.groovy      |    6 +-
 ...lesExcludesAndClassifiersIntegrationTest.groovy |    9 +-
 ...lesGroovyCustomizedLayoutIntegrationTest.groovy |   16 +-
 ...SamplesGroovyMultiProjectIntegrationTest.groovy |   12 +-
 .../SamplesGroovyQuickstartIntegrationTest.groovy  |   16 +-
 .../SamplesJavaApiAndImplIntegrationTest.groovy    |    2 +-
 .../samples/SamplesJavaBaseIntegrationTest.groovy  |   16 +-
 ...mplesJavaCustomizedLayoutIntegrationTest.groovy |   16 +-
 .../SamplesJavaMultiProjectIntegrationTest.groovy  |   28 +-
 .../SamplesJavaOnlyIfIntegrationTest.groovy        |   10 +-
 ...esJavaProjectWithIntTestsIntegrationTest.groovy |   14 +-
 .../SamplesJavaQuickstartIntegrationTest.groovy    |   20 +-
 ...SamplesMixedJavaAndGroovyIntegrationTest.groovy |   17 +-
 .../SamplesMixedJavaAndScalaIntegrationTest.groovy |   17 +-
 .../SamplesRepositoriesIntegrationTest.groovy      |    9 +-
 ...plesScalaCustomizedLayoutIntegrationTest.groovy |   16 +-
 .../SamplesScalaQuickstartIntegrationTest.groovy   |   17 +-
 .../samples/SamplesScalaZincIntegrationTest.groovy |   13 +-
 .../SamplesWebProjectIntegrationTest.groovy        |    4 +-
 .../SamplesWebQuickstartIntegrationTest.groovy     |    7 +-
 .../build.gradle                                   |    2 +-
 .../shared/build.gradle                            |    2 +-
 .../canUseANonStandardBuildDir/build.gradle        |    2 +-
 .../projectWithConfigurationHierarchy.gradle       |   58 -
 .../fixtures/AbstractAutoTestedSamplesTest.groovy  |    2 +-
 .../fixtures/AbstractCompatibilityTestRunner.java  |   65 +-
 .../fixtures/AbstractDelegatingGradleExecuter.java |   49 -
 .../AbstractDependencyResolutionTest.groovy        |   66 ++
 .../fixtures/AbstractGradleExecuter.java           |  430 -------
 .../fixtures/AbstractIntegrationSpec.groovy        |   59 +-
 .../fixtures/AbstractIntegrationTest.java          |   84 +-
 .../integtests/fixtures/ArtifactBuilder.java       |   28 -
 .../fixtures/BasicGradleDistribution.java          |   83 --
 .../fixtures/CrossVersionIntegrationSpec.groovy    |   44 +-
 .../fixtures/CrossVersionTestRunner.groovy         |   68 +-
 .../integtests/fixtures/DaemonGradleExecuter.java  |   74 --
 .../fixtures/EmbeddedDaemonGradleExecuter.java     |  109 --
 .../integtests/fixtures/ExecutionFailure.java      |   34 -
 .../integtests/fixtures/ExecutionResult.java       |   62 -
 .../integtests/fixtures/ForkingGradleExecuter.java |  197 ----
 .../integtests/fixtures/ForkingGradleHandle.java   |  116 --
 .../fixtures/GradleBackedArtifactBuilder.java      |   50 -
 .../integtests/fixtures/GradleDistribution.java    |  241 ----
 .../fixtures/GradleDistributionExecuter.java       |  302 -----
 .../gradle/integtests/fixtures/GradleExecuter.java |  204 ----
 .../gradle/integtests/fixtures/GradleHandle.java   |   28 -
 .../gradle/integtests/fixtures/IgnoreVersions.java |   28 +
 .../fixtures/InProcessGradleExecuter.java          |  387 -------
 .../fixtures/JUnitTestExecutionResult.groovy       |  181 ---
 .../integtests/fixtures/MavenFileModule.groovy     |  294 -----
 .../integtests/fixtures/MavenFileRepository.groovy |   41 -
 .../integtests/fixtures/MavenHttpModule.groovy     |  152 ---
 .../integtests/fixtures/MavenHttpRepository.groovy |   70 --
 .../gradle/integtests/fixtures/MavenModule.groovy  |   42 -
 .../org/gradle/integtests/fixtures/MavenPom.groovy |   46 -
 .../integtests/fixtures/MavenRepository.groovy     |   27 -
 .../gradle/integtests/fixtures/MavenScope.groovy   |   36 -
 .../fixtures/MultiVersionIntegrationSpec.groovy    |    4 -
 .../fixtures/OutputScrapingExecutionFailure.java   |   91 --
 .../fixtures/OutputScrapingExecutionResult.java    |  137 ---
 .../fixtures/OutputScrapingGradleHandle.java       |   27 -
 .../fixtures/ParallelForkingGradleExecuter.java    |   43 -
 .../fixtures/ParallelForkingGradleHandle.java      |   76 --
 .../fixtures/ParallelOutputMatcher.groovy          |   51 -
 .../fixtures/PreviousGradleVersionExecuter.groovy  |  178 ---
 .../fixtures/ProgressLoggingFixture.groovy         |   83 --
 .../fixtures/RedirectMavenCentral.groovy           |   46 -
 .../integtests/fixtures/ReleasedVersions.java      |   59 -
 .../org/gradle/integtests/fixtures/RuleHelper.java |   56 -
 .../org/gradle/integtests/fixtures/Sample.java     |   14 +-
 .../fixtures/SequentialOutputMatcher.groovy        |   92 --
 .../integtests/fixtures/TestExecutionResult.java   |   29 -
 .../fixtures/TestNGExecutionResult.groovy          |    2 +-
 .../gradle/integtests/fixtures/TestResources.java  |   25 +-
 .../fixtures/UnexpectedBuildFailure.java           |   27 -
 .../fixtures/UserGuideSamplesRunner.groovy         |  138 ++-
 .../executer/AbstractDelegatingGradleExecuter.java |   52 +
 .../fixtures/executer/AbstractGradleExecuter.java  |  602 ++++++++++
 .../fixtures/executer/ArtifactBuilder.java         |   28 +
 .../fixtures/executer/DaemonGradleExecuter.java    |   83 ++
 .../executer/DefaultGradleDistribution.java        |  148 +++
 .../executer/DependencyResolutionFailure.groovy    |   45 +
 .../executer/DetailedExecutionFailure.groovy       |   36 +
 .../executer/EmbeddedDaemonGradleExecuter.java     |  114 ++
 .../fixtures/executer/ExecutionFailure.java        |   38 +
 .../fixtures/executer/ExecutionResult.java         |   67 ++
 .../fixtures/executer/ForkingGradleExecuter.java   |  215 ++++
 .../fixtures/executer/ForkingGradleHandle.java     |  116 ++
 .../executer/GradleBackedArtifactBuilder.java      |   50 +
 .../executer/GradleContextualExecuter.java         |  115 ++
 .../fixtures/executer/GradleDistribution.java      |   92 ++
 .../fixtures/executer/GradleExecuter.java          |  233 ++++
 .../integtests/fixtures/executer/GradleHandle.java |   28 +
 .../fixtures/executer/InProcessGradleExecuter.java |  416 +++++++
 .../executer/IntegrationTestBuildContext.java      |   83 ++
 .../fixtures/executer/OutputScraper.groovy         |   64 ++
 .../executer/OutputScrapingExecutionFailure.java   |  100 ++
 .../executer/OutputScrapingExecutionResult.java    |  143 +++
 .../executer/OutputScrapingGradleHandle.java       |   27 +
 .../executer/ParallelForkingGradleExecuter.java    |   43 +
 .../executer/ParallelForkingGradleHandle.java      |   76 ++
 .../fixtures/executer/ParallelOutputMatcher.groovy |   51 +
 .../executer/ProgressLoggingFixture.groovy         |   86 ++
 .../fixtures/executer/RedirectMavenCentral.groovy  |   46 +
 .../executer/ReleasedGradleDistribution.groovy     |   78 ++
 .../executer/SequentialOutputMatcher.groovy        |   92 ++
 .../UnderDevelopmentGradleDistribution.java        |   34 +
 .../fixtures/executer/UnexpectedBuildFailure.java  |   27 +
 .../versions/ClasspathVersionJsonSource.java       |   58 +
 .../versions/IsTestableGradleVersionSpec.groovy    |   39 +
 .../fixtures/versions/ReleasedGradleVersion.java   |   63 +
 .../versions/ReleasedVersionDistributions.java     |  106 ++
 .../versions/VersionWebServiceJsonParser.java      |   85 ++
 .../fixtures/versions/VersionsInfo.groovy          |   78 --
 .../gradle/test/fixtures/ivy/IvyDescriptor.groovy  |   10 +-
 .../gradle/test/fixtures/ivy/IvyFileModule.groovy  |    8 +-
 .../test/fixtures/ivy/IvyFileRepository.groovy     |    2 +-
 .../gradle/test/fixtures/ivy/IvyHttpModule.groovy  |   39 +-
 .../test/fixtures/ivy/IvyHttpRepository.groovy     |    8 +
 .../org/gradle/test/fixtures/ivy/IvyModule.java    |    2 +-
 .../test/fixtures/maven/BasicHttpResource.groovy   |   46 +
 .../fixtures/maven/DefaultMavenMetaData.groovy     |   52 +
 .../gradle/test/fixtures/maven/HttpArtifact.groovy |   71 ++
 .../gradle/test/fixtures/maven/HttpResource.groovy |   56 +
 .../test/fixtures/maven/M2Installation.groovy      |   65 ++
 .../test/fixtures/maven/MavenFileModule.groovy     |  354 ++++++
 .../test/fixtures/maven/MavenFileRepository.groovy |   43 +
 .../test/fixtures/maven/MavenHttpArtifact.groovy   |   45 +
 .../test/fixtures/maven/MavenHttpModule.groovy     |  121 ++
 .../test/fixtures/maven/MavenHttpRepository.groovy |   67 ++
 .../test/fixtures/maven/MavenMetaData.groovy       |   22 +
 .../gradle/test/fixtures/maven/MavenModule.groovy  |   52 +
 .../org/gradle/test/fixtures/maven/MavenPom.groovy |   49 +
 .../test/fixtures/maven/MavenRepository.groovy     |   27 +
 .../gradle/test/fixtures/maven/MavenScope.groovy   |   38 +
 .../test/fixtures/maven/MetaDataArtifact.groovy    |   48 +
 .../test/fixtures/maven/PomHttpArtifact.groovy     |   57 +
 .../test/fixtures/server/http/HttpServer.groovy    |   60 +-
 .../test/fixtures/server/sftp/SFTPServer.groovy    |   17 +-
 .../fixtures/executer/OutputScraperTest.groovy     |  100 ++
 .../IsTestableGradleVersionSpecTest.groovy         |   59 +
 .../ReleasedVersionDistributionsTest.groovy        |   66 ++
 .../VersionWebServiceJsonParserTest.groovy         |  115 ++
 .../fixtures/versions/VersionsInfoTest.groovy      |   99 --
 .../fixtures/DefaultTestExecutionResult.groovy     |  184 +++
 .../fixtures/TestClassExecutionResult.java         |    0
 .../integtests/fixtures/TestExecutionResult.java   |   29 +
 .../test/fixtures/concurrent/BlockTarget.groovy    |   34 +
 .../test/fixtures/concurrent/ConcurrentSpec.groovy |  105 ++
 .../test/fixtures/concurrent/Duration.groovy       |   34 +
 .../gradle/test/fixtures/concurrent/Instant.groovy |   45 +
 .../test/fixtures/concurrent/InstantFactory.groovy |   21 +
 .../test/fixtures/concurrent/Instants.groovy       |  115 ++
 .../test/fixtures/concurrent/NamedInstant.groovy   |   36 +
 .../test/fixtures/concurrent/NamedOperation.groovy |   54 +
 .../fixtures/concurrent/OperationListener.groovy   |   23 +
 .../test/fixtures/concurrent/Operations.groovy     |   77 ++
 .../gradle/test/fixtures/concurrent/Range.groovy   |   38 +
 .../test/fixtures/concurrent/TestExecutor.groovy   |  128 +++
 .../fixtures/concurrent/TestExecutorFactory.groovy |   32 +
 .../concurrent/TestStoppableExecutor.groovy        |   76 ++
 .../test/fixtures/concurrent/TestThread.groovy     |   36 +
 .../fixtures/concurrent/TestThreadListener.groovy  |   22 +
 .../test/fixtures/file/TestDirectoryProvider.java  |   33 +
 .../file/TestDirectoryProviderFinder.groovy        |   35 +
 .../org/gradle/test/fixtures/file/TestFile.java    |  555 +++++++++
 .../test/fixtures/file/TestFileHelper.groovy       |  216 ++++
 .../file/TestNameTestDirectoryProvider.java        |  128 +++
 .../test/fixtures/file/TestWorkspaceBuilder.groovy |   54 +
 .../testing/internal/util/ExceptionAssert.groovy   |   61 -
 .../testing/internal/util/GradlewRunner.java       |    3 +
 .../main/groovy/org/gradle/util/Assertions.groovy  |   45 +
 .../src/main/groovy/org/gradle/util/Resources.java |    4 +-
 .../main/groovy/org/gradle/util/RuleHelper.java    |   57 +
 .../groovy/org/gradle/util/TemporaryFolder.java    |  114 --
 .../groovy/org/gradle/util/TestDirHelper.groovy    |   49 -
 .../src/main/groovy/org/gradle/util/TestFile.java  |  559 ---------
 .../groovy/org/gradle/util/TestFileContext.java    |   23 -
 .../groovy/org/gradle/util/TestFileHelper.groovy   |  210 ----
 .../groovy/org/gradle/util/TestPrecondition.groovy |    3 +
 .../fixtures/concurrent/ConcurrentSpecTest.groovy  |  350 ++++++
 .../groovy/org/gradle/util/AssertionsTest.groovy   |   61 +
 .../gradle/util/TempDirIsUniquePerTestSpec.groovy  |   53 +
 .../ivy/IvyCustomPublishIntegrationTest.groovy     |   52 +-
 .../ivy/IvyHttpPublishIntegrationTest.groovy       |   10 +-
 .../ivy/IvyLocalPublishIntegrationTest.groovy      |   89 +-
 ...vyPublishDescriptorModificationIntegTest.groovy |   30 +-
 .../IvyPublishMultipleReposIntegrationTest.groovy  |   17 +-
 .../ivy/IvySFtpPublishIntegrationTest.groovy       |   97 --
 .../IvySingleProjectPublishIntegrationTest.groovy  |   61 -
 .../ivy/SamplesIvyPublishIntegrationTest.groovy    |   30 +-
 .../api/publish/ivy/IvyModuleDescriptor.java       |   21 +-
 .../org/gradle/api/publish/ivy/IvyPublication.java |   14 +
 .../ivy/internal/DefaultIvyModuleDescriptor.java   |   32 +-
 .../ivy/internal/DefaultIvyPublication.java        |   54 +-
 .../ivy/internal/IvyModuleDescriptorInternal.java  |   13 +-
 .../ivy/internal/IvyNormalizedPublication.java     |   49 +
 .../ivy/internal/IvyPublicationInternal.java       |   13 +-
 .../api/publish/ivy/internal/IvyPublisher.java     |   40 +
 .../api/publish/ivy/plugins/IvyPublishPlugin.java  |   30 +-
 .../publish/ivy/tasks/GenerateIvyDescriptor.java   |  182 +++
 .../publish/ivy/tasks/PublishToIvyRepository.java  |   66 +-
 ...tionDynamicDescriptorGenerationTaskCreator.java |   83 ++
 .../internal/IvyPublishDynamicTaskCreator.java     |   49 +-
 .../ivy/internal/DefaultIvyPublicationTest.groovy  |   45 +-
 .../ivy/plugins/IvyPublishPluginTest.groovy        |    5 +-
 .../ivy/tasks/PublishToIvyRepositoryTest.groovy    |   71 +-
 ...namicDescriptorGenerationTaskCreatorTest.groovy |   89 ++
 .../javascript/base/JavaScriptBasePlugin.groovy    |   11 +-
 .../javascript/base/JavaScriptExtension.java       |   37 -
 .../base/JavaScriptRepositoriesExtension.java      |   76 ++
 .../base/JavaScriptBasePluginTest.groovy           |    9 +-
 .../simple/SimpleHttpFileServerFactoryTest.groovy  |    9 +-
 .../base/JavaScriptBasePluginTestFixtures.groovy   |    4 +-
 subprojects/launcher/launcher.gradle               |    6 +-
 .../GradleConfigurabilityIntegrationSpec.groovy    |   22 +-
 .../gradle/launcher/SystemClassLoaderTest.groovy   |    4 +-
 .../daemon/DaemonFeedbackIntegrationSpec.groovy    |   37 +-
 .../DaemonInitScriptHandlingIntegrationTest.groovy |   80 ++
 ...itialCommunicationFailureIntegrationSpec.groovy |   10 +-
 .../launcher/daemon/DaemonIntegrationSpec.groovy   |   26 +-
 .../launcher/daemon/DaemonLifecycleSpec.groovy     |    7 +-
 .../DaemonSystemPropertiesIntegrationTest.groovy   |    8 +-
 .../daemon/DispachingFailureIntegrationSpec.groovy |    4 +-
 .../launcher/daemon/EmbeddedDaemonSmokeTest.groovy |   71 ++
 .../daemon/SingleUseDaemonIntegrationTest.groovy   |   33 +-
 .../daemon/StoppingDaemonIntegrationSpec.groovy    |    7 +-
 .../daemon/client/DaemonClientServicesSupport.java |    5 +-
 .../daemon/client/DefaultDaemonConnector.java      |    6 +-
 .../daemon/client/DefaultDaemonStarter.java        |    3 +-
 .../daemon/configuration/DaemonParameters.java     |    6 +
 .../daemon/context/DaemonContextBuilder.java       |    4 +-
 .../gradle/launcher/daemon/registry/DaemonDir.java |    3 +-
 .../daemon/registry/PersistentDaemonRegistry.java  |    2 +-
 .../daemon/server/DaemonTcpServerConnector.java    |   18 +-
 .../launcher/daemon/server/exec/LogToClient.java   |    2 +-
 .../internal/provider/ConfiguringBuildAction.java  |    5 +-
 .../launcher/cli/BuildActionsFactoryTest.groovy    |    4 +-
 .../cli/CommandLineActionFactoryTest.groovy        |    6 +-
 .../launcher/daemon/EmbeddedDaemonSmokeTest.groovy |   71 --
 .../daemon/client/DaemonClientServicesTest.groovy  |    6 +-
 .../client/DefaultDaemonConnectorTest.groovy       |   36 +-
 .../daemon/configuration/CurrentProcessTest.groovy |    7 +-
 .../configuration/DaemonParametersTest.groovy      |   24 +-
 .../context/DaemonCompatibilitySpecSpec.groovy     |    6 +-
 .../diagnostics/DaemonDiagnosticsTest.groovy       |    4 +-
 .../registry/DaemonRegistryServicesTest.groovy     |    6 +-
 .../registry/PersistentDaemonRegistryTest.groovy   |    4 +-
 .../DaemonServerExceptionHandlingTest.groovy       |    8 +-
 .../daemon/server/DaemonServicesTest.groovy        |    7 +-
 .../provider/ConfiguringBuildActionTest.groovy     |    5 +-
 subprojects/maven/maven.gradle                     |    1 +
 .../AutoTestedSamplesMavenIntegrationTest.groovy   |   31 +
 .../maven/MavenConversionIntegrationTest.groovy    |   70 +-
 .../maven/MavenPublishBasicIntegTest.groovy        |  100 ++
 .../MavenPublishCrossVersionIntegrationTest.groovy |   83 ++
 .../publish/maven/MavenPublishHttpIntegTest.groovy |  184 +++
 .../publish/maven/MavenPublishJavaIntegTest.groovy |   68 ++
 .../maven/MavenPublishMultiProjectIntegTest.groovy |  226 ++++
 .../MavenPublishPomCustomisationIntegTest.groovy   |  142 +++
 .../SamplesMavenPublishIntegrationTest.groovy      |   44 +
 .../plugins/MavenPublishPluginIntegTest.groovy     |   33 +
 .../enforcerplugin/pom.xml                         |   42 +
 .../enforcerplugin/src/main/java/Foo.java          |   23 +
 .../org/gradle/api/artifacts/maven/MavenPom.java   |    6 +-
 .../groovy/org/gradle/api/plugins/MavenPlugin.java |    7 +-
 .../api/plugins/maven/internal/Maven2Gradle.groovy |    2 +-
 .../internal/CustomTaskFactoryDeployerFactory.java |   43 +
 .../maven/internal/DefaultDeployerFactory.java     |   14 +-
 .../maven/internal/DefaultMavenPom.java            |   24 +-
 .../DefaultMavenRepositoryHandlerConvention.java   |   21 +-
 .../maven/internal/ant/AbstractMavenResolver.java  |    3 +-
 .../maven/internal/ant/CustomDeployTask.java       |   12 +
 .../ant/DefaultPomDependenciesConverter.java       |    7 +-
 .../internal/ant/NoInstallDeployTaskFactory.java   |   50 +
 .../org/gradle/api/publish/maven/MavenPom.java     |   65 ++
 .../gradle/api/publish/maven/MavenPublication.java |   68 ++
 .../publish/maven/internal/DefaultMavenPom.java    |   36 +
 .../maven/internal/DefaultMavenPublication.java    |   68 ++
 .../maven/internal/MavenDeployerConfigurer.java    |   55 +
 .../maven/internal/MavenNormalizedPublication.java |   65 ++
 .../publish/maven/internal/MavenPomInternal.java   |   27 +
 .../maven/internal/MavenProjectIdentity.java       |   29 +
 .../MavenProjectIdentityModuleAdapter.java         |   44 +
 .../maven/internal/MavenPublicationInternal.java   |   34 +
 .../internal/MavenPublishDynamicTaskCreator.java   |   85 ++
 .../MavenPublishLocalDynamicTaskCreator.java       |   64 ++
 .../api/publish/maven/internal/MavenPublisher.java |  111 ++
 .../internal/ModuleBackedMavenProjectIdentity.java |   44 +
 .../org/gradle/api/publish/maven/package-info.java |   25 +
 .../publish/maven/plugins/MavenPublishPlugin.java  |  107 ++
 .../api/publish/maven/plugins/package-info.java    |   25 +
 .../publish/maven/tasks/PublishToMavenLocal.java   |   58 +
 .../maven/tasks/PublishToMavenRepository.java      |  182 +++
 .../api/publish/maven/tasks/package-info.java      |   25 +
 .../gradle-plugins/maven-publish.properties        |    1 +
 .../org/gradle/api/plugins/MavenPluginTest.java    |    3 +-
 .../maven/internal/MavenProjectsCreatorSpec.groovy |   10 +-
 .../maven/internal/DefaultArtifactPomTest.java     |    6 +-
 .../maven/internal/DefaultMavenPomTest.groovy      |   22 +-
 ...aultMavenRepositoryHandlerConventionTest.groovy |   29 +-
 .../ModuleBackedMavenProjectIdentityTest.groovy    |   57 +
 .../maven/plugins/MavenPublishPluginTest.groovy    |  181 +++
 .../tasks/PublishToMavenRepositoryTest.groovy      |   33 +
 subprojects/messaging/messaging.gradle             |    1 +
 .../BroadcastMessagingIntegrationTest.groovy       |  340 ++++++
 .../remote/UnicastMessagingIntegrationTest.groovy  |  288 +++++
 .../internal/hub/MessageHubIntegrationTest.groovy  |  229 ++++
 .../messaging/dispatch/MethodInvocation.java       |    3 +-
 .../messaging/remote/ConnectionAcceptor.java       |   33 +
 .../gradle/messaging/remote/MessagingServer.java   |    2 +-
 .../gradle/messaging/remote/ObjectConnection.java  |    2 +-
 .../remote/internal/CompositeAddress.java          |   62 -
 .../remote/internal/DefaultIncomingBroadcast.java  |    9 +-
 .../remote/internal/DefaultMessageSerializer.java  |   42 +-
 .../remote/internal/DefaultMessagingClient.java    |   49 -
 .../remote/internal/DefaultMessagingServer.java    |   97 --
 .../internal/DefaultMultiChannelConnection.java    |   65 --
 .../internal/DefaultMultiChannelConnector.java     |   74 --
 .../remote/internal/DefaultObjectConnection.java   |   66 --
 .../remote/internal/DefaultOutgoingBroadcast.java  |    6 +-
 .../internal/HandshakeIncomingConnector.java       |   80 --
 .../internal/HandshakeOutgoingConnector.java       |   46 -
 .../remote/internal/IncomingConnector.java         |   16 +-
 .../internal/IncomingMethodInvocationHandler.java  |   55 -
 .../gradle/messaging/remote/internal/Message.java  |   10 +-
 .../remote/internal/MessageSerializer.java         |   20 +-
 .../remote/internal/MessagingServices.java         |   72 +-
 .../remote/internal/MultiChannelConnection.java    |   46 -
 .../remote/internal/MultiChannelConnector.java     |   27 -
 .../remote/internal/OutgoingConnector.java         |   14 +-
 .../internal/OutgoingMethodInvocationHandler.java  |   44 -
 .../remote/internal/PlaceholderException.java      |   15 +-
 .../remote/internal/hub/ConnectionSet.java         |   67 ++
 .../remote/internal/hub/ConnectionState.java       |   62 +
 .../remote/internal/hub/HubStateListener.java      |   32 +
 .../remote/internal/hub/IncomingQueue.java         |   32 +
 .../internal/hub/InterHubMessageSerializer.java    |  124 ++
 .../messaging/remote/internal/hub/MessageHub.java  |  382 +++++++
 .../internal/hub/MessageHubBackedClient.java       |   52 +
 .../hub/MessageHubBackedObjectConnection.java      |   59 +
 .../internal/hub/MessageHubBackedServer.java       |   63 +
 .../internal/hub/MethodInvocationSerializer.java   |  141 +++
 .../remote/internal/hub/OutgoingQueue.java         |   51 +
 .../internal/hub/RejectedMessageListener.java      |   27 +
 .../internal/hub/protocol/ChannelIdentifier.java   |   51 +
 .../internal/hub/protocol/ChannelMessage.java      |   45 +
 .../internal/hub/protocol/ConnectionClosed.java    |   36 +
 .../hub/protocol/ConnectionEstablished.java        |   36 +
 .../remote/internal/hub/protocol/EndOfStream.java  |   29 +
 .../internal/hub/protocol/InterHubMessage.java     |   39 +
 .../internal/hub/protocol/RejectedMessage.java     |   45 +
 .../remote/internal/hub/protocol/Routable.java     |   21 +
 .../remote/internal/hub/queue/EndPointQueue.java   |   61 +
 .../internal/hub/queue/MultiChannelQueue.java      |   63 +
 .../internal/hub/queue/MultiEndPointQueue.java     |  101 ++
 .../internal/hub/queue/QueueInitializer.java       |   56 +
 .../remote/internal/inet/MulticastConnection.java  |    8 +-
 .../remote/internal/inet/SocketConnection.java     |   25 +-
 .../remote/internal/inet/TcpIncomingConnector.java |   59 +-
 .../remote/internal/inet/TcpOutgoingConnector.java |   26 +-
 .../remote/internal/protocol/ConnectRequest.java   |   32 -
 .../remote/internal/protocol/DiscoveryMessage.java |    4 +-
 .../protocol/DiscoveryProtocolSerializer.java      |  248 ++--
 .../serialize/DataStreamBackedSerializer.java      |   36 +
 .../messaging/serialize/DefaultSerializer.java     |   54 +
 .../gradle/messaging/serialize/ObjectReader.java   |   21 +
 .../gradle/messaging/serialize/ObjectWriter.java   |   21 +
 .../org/gradle/messaging/serialize/Serializer.java |    7 +
 .../messaging/serialize/kryo/JavaSerializer.java   |   65 ++
 .../serialize/kryo/KryoAwareSerializer.java        |   28 +
 .../messaging/serialize/kryo/KryoSerializer.java   |   59 +
 .../serialize/kryo/TypeSafeSerializer.java         |   50 +
 .../actor/internal/DefaultActorFactorySpec.groovy  |  306 +++++
 .../actor/internal/DefaultActorFactoryTest.groovy  |  290 -----
 .../remote/internal/CompositeAddressTest.groovy    |   46 -
 .../internal/DefaultMessagingClientTest.groovy     |   42 -
 .../internal/DefaultMessagingServerTest.groovy     |   99 --
 .../internal/DefaultObjectConnectionTest.java      |  237 ----
 .../internal/HandshakeIncomingConnectorTest.groovy |   83 --
 .../internal/HandshakeOutgoingConnectorTest.groovy |   65 --
 .../messaging/remote/internal/MessageTest.groovy   |   47 +
 .../internal/PlaceholderExceptionTest.groovy       |   20 +-
 .../remote/internal/hub/ConnectionSetTest.groovy   |   84 ++
 .../hub/InterHubMessageSerializerTest.groovy       |  118 ++
 .../internal/hub/MessageHubBackedClientTest.groovy |   56 +
 .../internal/hub/MessageHubBackedServerTest.groovy |   72 ++
 .../remote/internal/hub/MessageHubTest.groovy      |  680 +++++++++++
 .../hub/MethodInvocationSerializerTest.groovy      |  118 ++
 .../hub/protocol/ChannelIdentifierTest.groovy      |   34 +
 .../internal/hub/queue/AbstractQueueTest.groovy    |   46 +
 .../hub/queue/MultiChannelQueueTest.groovy         |  132 +++
 .../hub/queue/MultiEndPointQueueTest.groovy        |  165 +++
 .../internal/hub/queue/QueueInitializerTest.groovy |   87 ++
 .../inet/TcpConnectorConcurrencyTest.groovy        |    8 +-
 .../remote/internal/inet/TcpConnectorTest.groovy   |  140 ++-
 .../DiscoveryProcotolSerializerTest.groovy         |   22 +-
 .../serialize/DefaultSerializerTest.groovy         |   36 +
 .../jna/AbstractProcessEnvironment.java            |   16 +-
 .../jna/LibCBackedProcessEnvironment.java          |    6 +-
 .../nativeplatform/services/NativeServices.java    |   13 +-
 .../filesystem/CommonFileSystemTest.groovy         |    4 +-
 .../FileSystemServicesOnLinuxTest.groovy           |    6 +-
 .../filesystem/FileSystemServicesOnMacTest.groovy  |    6 +-
 .../FileSystemServicesOnUnknownOsTest.groovy       |    8 +-
 .../FileSystemServicesOnWindowsTest.groovy         |    6 +-
 .../jdk7/PosixJdk7FilePermissionHandlerTest.groovy |    6 +-
 .../jna/ProcessEnvironmentTest.groovy              |   10 +-
 ...CrossVersionCompatibilityIntegrationTest.groovy |   12 +-
 .../integtests/openapi/GradleRunnerTest.groovy     |   11 +-
 .../gradle/integtests/openapi/OpenApiFixture.java  |   22 +-
 .../gradle/integtests/openapi/OpenApiUiTest.groovy |   47 +-
 .../integtests/openapi/OutputUILordTest.groovy     |   25 +-
 .../openapi/external/ExternalUtilityTest.groovy    |   24 +-
 subprojects/performance/performance.gradle         |   88 +-
 .../performance/src/configPlugin/ConfigPlugin.java |   31 +
 subprojects/performance/src/generator.groovy       |   59 +-
 .../DependencyResolutionStressTest.groovy          |  280 -----
 .../org/gradle/peformance/PerformanceTest.groovy   |  144 ---
 .../performance/CleanBuildPerformanceTest.groovy   |   50 +
 .../DependencyReportPerformanceTest.groovy         |   47 +
 .../DependencyResolutionStressTest.groovy          |  282 +++++
 .../IdeIntegrationPerformanceTest.groovy           |   69 ++
 .../TestExecutionPerformanceTest.groovy            |   53 +
 .../UpToDateBuildPerformanceTest.groovy            |   49 +
 subprojects/performance/src/templates/build.gradle |  148 ---
 subprojects/performance/src/templates/build.xml    |   70 --
 .../src/templates/config-inject/build.gradle       |   60 +
 .../src/templates/heap-capture/build.gradle        |   27 +
 .../src/templates/java-config/build.gradle         |    7 +
 .../performance/src/templates/minimal/build.gradle |    1 +
 .../src/templates/plain-ant-compile/build.gradle   |   40 +
 subprojects/performance/src/templates/pom.xml      |  102 --
 .../{ => project-with-source}/Production.groovy    |    0
 .../{ => project-with-source}/Production.java      |    0
 .../{ => project-with-source}/Production.scala     |    0
 .../{ => project-with-source}/Test.groovy          |    0
 .../templates/{ => project-with-source}/Test.java  |    0
 .../templates/{ => project-with-source}/Test.scala |    0
 .../src/templates/project-with-source/build.gradle |   57 +
 .../src/templates/project-with-source/build.xml    |   56 +
 .../src/templates/project-with-source/pom.xml      |  101 ++
 .../src/templates/root-project/build.gradle}       |    0
 .../src/templates/root-project/build.xml           |   13 +
 .../performance/src/templates/root-project/pom.xml |   10 +
 .../performance/src/templates/with-junit/Test.java |   15 +
 .../src/templates/with-testng/Test.java            |   15 +
 .../src/templates/with-testng/build.gradle         |    7 +
 .../Test.java}                                     |    0
 .../Test.java}                                     |    0
 .../src/templates/with-verbose-testng/build.gradle |    7 +
 .../gradle/peformance/fixture/AmountTest.groovy    |  231 ----
 .../gradle/peformance/fixture/DurationTest.groovy  |   59 -
 .../fixture/PerformanceResultsTest.groovy          |  202 ----
 .../peformance/fixture/PrettyCalculatorSpec.groovy |   45 -
 .../org/gradle/peformance/fixture/UnitsTest.groovy |   34 -
 .../gradle/performance/fixture/AmountTest.groovy   |  231 ++++
 .../gradle/performance/fixture/DurationTest.groovy |   59 +
 .../fixture/PerformanceResultsTest.groovy          |  297 +++++
 .../fixture/PrettyCalculatorSpec.groovy            |   45 +
 .../gradle/performance/fixture/UnitsTest.groovy    |   34 +
 .../org/gradle/peformance/fixture/Amount.java      |  160 ---
 .../org/gradle/peformance/fixture/DataAmount.java  |   37 -
 .../gradle/peformance/fixture/DataCollector.java   |   27 -
 .../org/gradle/peformance/fixture/Duration.java    |   45 -
 .../peformance/fixture/MeasuredOperation.groovy    |   41 -
 .../fixture/MeasuredOperationList.groovy           |   50 -
 .../peformance/fixture/MemoryInfoCollector.groovy  |   34 -
 .../peformance/fixture/PerformanceResults.groovy   |  136 ---
 .../fixture/PerformanceTestRunner.groovy           |   89 --
 .../peformance/fixture/PrettyCalculator.groovy     |   49 -
 .../peformance/fixture/TestProjectLocator.groovy   |   38 -
 .../org/gradle/peformance/fixture/Units.java       |  193 ----
 .../org/gradle/performance/fixture/Amount.java     |  162 +++
 .../performance/fixture/BaselineVersion.groovy     |   79 ++
 .../org/gradle/performance/fixture/DataAmount.java |   37 +
 .../gradle/performance/fixture/DataCollector.java  |   27 +
 .../org/gradle/performance/fixture/Duration.java   |   45 +
 .../performance/fixture/MeasuredOperation.groovy   |   41 +
 .../fixture/MeasuredOperationList.groovy           |   65 ++
 .../performance/fixture/MemoryInfoCollector.groovy |   34 +
 .../performance/fixture/PerformanceResults.groovy  |   76 ++
 .../fixture/PerformanceTestRunner.groovy           |  111 ++
 .../performance/fixture/PrettyCalculator.groovy    |   49 +
 .../performance/fixture/TestProjectLocator.groovy  |   38 +
 .../org/gradle/performance/fixture/Units.java      |  193 ++++
 .../api/plugins/BasePluginIntegrationTest.groovy   |    2 +-
 .../gradle/api/plugins/BuildSrcPluginTest.groovy   |    2 +-
 .../JavaLibraryDistributionIntegrationTest.groovy  |  141 +++
 .../GitHubDependenciesPluginIntegrationTest.groovy |   55 -
 .../groovy/GroovyBasePluginIntegrationTest.groovy  |   80 ++
 .../AntForkingGroovyCompilerIntegrationTest.groovy |    2 +-
 ...ntInProcessGroovyCompilerIntegrationTest.groovy |    2 +-
 .../ApiGroovyCompilerIntegrationSpec.groovy        |    4 +-
 .../BasicGroovyCompilerIntegrationSpec.groovy      |   88 +-
 .../IncrementalGroovyCompileIntegrationTest.groovy |   14 +-
 .../compile/InvokeDynamicGroovyCompilerSpec.groovy |    2 +-
 .../JreJavaHomeGroovyIntegrationTest.groovy        |    4 +-
 .../IncrementalJavaCompileIntegrationTest.groovy   |   30 +-
 .../JreJavaHomeJavaIntegrationTest.groovy          |    2 +-
 .../gradle/javadoc/JavadocIntegrationTest.groovy   |   15 +-
 .../testing/TestEnvironmentIntegrationTest.groovy  |   10 +-
 .../TestOutputListenerIntegrationTest.groovy       |    6 +-
 .../testing/TestReportIntegrationTest.groovy       |   85 ++
 .../gradle/testing/TestingIntegrationTest.groovy   |   10 +-
 .../junit/JUnitCrossVersionIntegrationSpec.groovy  |   61 +
 .../testing/junit/JUnitIntegrationTest.groovy      |  145 +--
 .../junit/JUnitLoggingIntegrationTest.groovy       |   46 +-
 .../testng/SampleTestNGIntegrationTest.groovy      |   17 +-
 .../testing/testng/TestNGIntegrationTest.groovy    |   56 +-
 .../testng/TestNGLoggingIntegrationTest.groovy     |    2 +-
 ...NGProducesJUnitXmlResultsIntegrationTest.groovy |   64 +-
 .../TestNGProducesOldReportsIntegrationTest.groovy |   62 +-
 .../src/test/groovy/Person2.groovy                 |    5 -
 .../src/test/groovy/PersonTest.groovy              |    4 +
 .../badCodeBreaksBuild/build.gradle                |    9 -
 .../src/main/groovy/BrokenClass.groovy             |    5 -
 .../badJavaCodeBreaksBuild/build.gradle            |    9 -
 .../build.gradle                                   |    2 +-
 .../compileBadCode/build.gradle                    |    5 +
 .../src/main/groovy/BrokenClass.groovy             |    4 +
 .../compileBadJavaCode/build.gradle                |    5 +
 .../src/main/groovy/BrokenClass.java               |    0
 .../src/main/groovy/OkClass.groovy                 |    0
 .../compileGoodCode/build.gradle                   |    9 +
 .../compileGoodCode/src/main/groovy/Address.groovy |    9 +
 .../compileGoodCode/src/main/groovy/Person.groovy  |    9 +
 .../build.gradle                                   |   21 +
 .../src/main/java/Person.java                      |   23 +
 .../src/taglet/java/LocaleAwareTaglet.java         |   67 ++
 .../canRunTestsUsingJUnit/build.gradle             |   21 +
 .../build.gradle                                   |    2 +-
 .../test/groovy/org/gradle/SystemErrTest.groovy    |    6 +-
 .../build.gradle                                   |    2 +-
 .../canHaveMultipleTestTaskInstances/build.gradle  |    2 +-
 .../canRunSingleTests/build.gradle                 |    2 +-
 .../detectsTestClasses/build.gradle                |    2 +-
 .../executesTestsInCorrectEnvironment/build.gradle |    2 +-
 .../src/test/java/org/gradle/OkTest.java           |    3 +-
 .../JUnitIntegrationTest/junit4Tests/build.gradle  |    2 +-
 .../test/java/org/gradle/CustomIgnoredTest.java    |   71 ++
 .../junit4_4Tests/build.gradle                     |    9 -
 .../build.gradle                                   |    2 +-
 .../src/test/java/org/gradle/BrokenTest.java       |    2 +-
 .../src/test/java/org/gradle/CustomException.java  |   17 +
 .../java/org/gradle/UnserializableException.java   |   24 +
 .../suitesOutputIsVisible/build.gradle             |    2 +-
 .../src/test/java/org/gradle/ASuite.java           |    1 +
 .../shared/build.gradle                            |    4 +-
 .../standardOutputLogging/build.gradle             |    4 +-
 .../groovyJdk15Failing/build.gradle                |    2 +-
 .../groovyJdk15Passing/build.gradle                |    2 +-
 .../javaJdk15Failing/build.gradle                  |    6 +-
 .../shared/build.gradle                            |    2 +-
 .../standardOutputLogging/build.gradle             |    2 +-
 .../org/gradle/api/internal/java/JavaLibrary.java  |   46 +
 .../gradle/api/internal/plugins/GroovyJarFile.java |   69 ++
 .../internal/plugins/StartScriptGenerator.groovy   |    3 +-
 .../tasks/testing/DecoratingTestDescriptor.java    |   12 +-
 .../api/internal/tasks/testing/TestFramework.java  |    2 -
 .../detection/AbstractTestFrameworkDetector.java   |    1 +
 .../detection/ClassFileExtractionManager.java      |    1 +
 .../junit/AllExceptIgnoredTestRunnerBuilder.java   |   67 ++
 .../testing/junit/JUnitTestClassProcessor.java     |    8 +-
 .../tasks/testing/junit/JUnitTestEventAdapter.java |   29 +-
 .../tasks/testing/junit/JUnitTestFramework.java    |   21 -
 .../testing/junit/JUnitXmlReportGenerator.java     |   61 -
 .../junit/TestNGJUnitXmlReportGenerator.java       |   99 --
 .../testing/junit/report/ClassPageRenderer.java    |   44 +-
 .../testing/junit/report/ClassTestResults.java     |   18 -
 .../testing/junit/report/DefaultTestReport.java    |  118 +-
 .../tasks/testing/junit/report/TestReporter.java   |    8 +-
 .../junit/result/AbstractTestResultProvider.java   |   70 ++
 .../junit/result/AggregateTestResultsProvider.java |   58 +
 .../result/Binary2JUnitXmlReportGenerator.java     |   73 ++
 .../BinaryResultBackedTestResultsProvider.java     |   31 +
 .../testing/junit/result/CachingFileWriter.java    |  100 ++
 .../testing/junit/result/JUnitXmlResultWriter.java |  126 ++
 .../testing/junit/result/TestClassResult.java      |   75 ++
 .../testing/junit/result/TestMethodResult.java     |   68 ++
 .../junit/result/TestReportDataCollector.java      |   92 ++
 .../testing/junit/result/TestResultSerializer.java |  133 +++
 .../testing/junit/result/TestResultsProvider.java  |   37 +
 .../tasks/testing/junit/result/XmlTestSuite.java   |  150 ---
 .../testing/junit/result/XmlTestSuiteFactory.java  |   51 -
 .../tasks/testing/results/DefaultTestResult.java   |   24 +-
 .../results/StateTrackingTestResultProcessor.java  |    4 -
 .../testing/testng/TestNGTestClassProcessor.java   |   21 +-
 .../tasks/testing/testng/TestNGTestFramework.java  |   32 +-
 .../internal/DefaultManifestMergeSpec.java         |    4 +-
 .../gradle/api/plugins/ApplicationPlugin.groovy    |   24 +-
 .../api/plugins/DistributionExtension.groovy       |   46 +
 .../org/gradle/api/plugins/GroovyBasePlugin.java   |   86 +-
 .../org/gradle/api/plugins/JavaBasePlugin.java     |    8 +-
 .../plugins/JavaLibraryDistributionPlugin.groovy   |   71 ++
 .../groovy/org/gradle/api/plugins/JavaPlugin.java  |    7 +
 .../plugins/github/GitHubDependenciesPlugin.groovy |   63 -
 .../plugins/github/GitHubDownloadsRepository.java  |   85 --
 .../github/GitHubRepositoryHandlerExtension.java   |   53 -
 .../internal/DefaultGitHubDownloadsRepository.java |  119 --
 .../tasks/application/CreateStartScripts.groovy    |    4 +-
 .../gradle/api/tasks/compile/GroovyCompile.java    |   17 +-
 .../groovy/org/gradle/api/tasks/testing/Test.java  |  254 +++--
 .../org/gradle/api/tasks/testing/TestReport.java   |  124 ++
 .../api/tasks/testing/testng/TestNGOptions.groovy  |   46 +-
 .../java-library-distribution.properties           |   17 +
 .../api/internal/plugins/GroovyJarFileTest.groovy  |   66 ++
 ...ndLineJavaCompilerArgumentsGeneratorTest.groovy |    9 +-
 .../compile/SimpleStaleClassCleanerTest.groovy     |   12 +-
 .../junit/JUnitTestClassProcessorTest.groovy       |   47 +-
 .../testing/junit/JUnitTestFrameworkTest.java      |   63 +-
 .../junit/report/DefaultTestReportTest.groovy      |  299 +++--
 .../Binary2JUnitXmlReportGeneratorSpec.groovy      |   78 ++
 .../junit/result/CachingFileWriterSpec.groovy      |   83 ++
 .../junit/result/JUnitXmlResultWriterSpec.groovy   |  142 +++
 .../junit/result/TestClassResultSpec.groovy        |   42 +
 .../result/TestReportDataCollectorSpec.groovy      |  163 +++
 .../junit/result/TestResultSerializerTest.groovy   |  103 ++
 .../logging/FullExceptionFormatterTest.groovy      |    4 +-
 .../logging/ShortExceptionFormatterTest.groovy     |    2 +-
 .../testng/TestNGTestClassProcessorTest.groovy     |  364 ++----
 .../testing/testng/TestNGTestFrameworkTest.groovy  |   66 ++
 .../testing/testng/TestNGTestFrameworkTest.java    |   98 --
 .../internal/DefaultManifestMergeSpecTest.groovy   |    8 +-
 .../archives/internal/DefaultManifestTest.groovy   |    6 +-
 .../api/plugins/ApplicationPluginTest.groovy       |   11 +
 .../gradle/api/plugins/GroovyBasePluginTest.groovy |   53 +-
 .../org/gradle/api/plugins/GroovyPluginTest.groovy |    7 +-
 .../gradle/api/plugins/JavaBasePluginTest.groovy   |   23 +-
 .../JavaLibraryDistributionPluginTest.groovy       |   57 +
 .../api/plugins/JavaPluginConventionTest.groovy    |    7 +-
 .../org/gradle/api/plugins/JavaPluginTest.groovy   |   25 +-
 .../github/GitHubDependenciesPluginTest.groovy     |   51 -
 .../api/tasks/compile/GroovyCompileTest.java       |   29 +-
 .../gradle/api/tasks/compile/JavaCompileTest.java  |    8 +-
 .../org/gradle/api/tasks/javadoc/JavadocTest.java  |    9 +-
 .../gradle/api/tasks/testing/TestReportTest.groovy |   56 +
 .../gradle/api/tasks/testing/TestTaskSpec.groovy   |   75 ++
 .../org/gradle/api/tasks/testing/TestTest.java     |   34 +-
 .../org/gradle/api/tasks/wrapper/WrapperTest.java  |    8 +-
 .../internal/JavadocOptionFileWriterTest.groovy    |    4 +-
 .../gradle/api/publish/PublishingExtension.java    |    3 +-
 .../api/publish/internal/PublishOperation.java     |   42 +
 .../api/publish/plugins/PublishingPlugin.java      |    9 +-
 .../DefaultPublicationContainerTest.groovy         |   18 -
 .../scala/ScalaBasePluginIntegrationTest.groovy    |   75 ++
 .../AntForkingScalaCompilerIntegrationTest.groovy  |    2 +-
 ...AntInProcessScalaCompilerIntegrationTest.groovy |    2 +-
 .../BasicScalaCompilerIntegrationTest.groovy       |    3 +-
 .../IncrementalScalaCompileIntegrationTest.groovy  |   73 +-
 .../ZincScalaCompilerIntegrationTest.groovy        |    1 -
 ...tForkingScalaCompilerJdk6IntegrationTest.groovy |    2 +-
 ...nProcessScalaCompilerJdk6IntegrationTest.groovy |    2 +-
 .../JreJavaHomeScalaIntegrationTest.groovy         |   11 +-
 .../scala/test/ScalaTestIntegrationTest.groovy     |   64 ++
 .../recompilesDependentClasses/build.gradle        |    1 -
 .../build.gradle                                   |    1 -
 .../build.gradle                                   |    1 -
 .../compilesJavaCodeIncrementally/build.gradle     |    1 -
 .../compilesScalaCodeIncrementally/build.gradle    |    1 -
 .../api/plugins/scala/ScalaBasePlugin.groovy       |   75 +-
 .../org/gradle/api/tasks/scala/ScalaCompile.java   |    8 +
 .../api/plugins/scala/ScalaBasePluginTest.groovy   |   48 +-
 .../api/plugins/scala/ScalaPluginTest.groovy       |   14 +-
 .../gradle/api/tasks/scala/ScalaCompileTest.java   |   27 +-
 .../plugins/signing/SigningSamplesSpec.groovy      |    2 +-
 .../plugins/signing/SigningProjectSpec.groovy      |    8 +-
 .../plugins/sonar/SonarSmokeIntegrationTest.groovy |   20 +-
 .../gradle/api/plugins/sonar/SonarAnalyze.groovy   |    3 +-
 .../tooling/AutoTestedSamplesToolingApiTest.groovy |    8 +-
 .../ConcurrentToolingApiIntegrationSpec.groovy     |   57 +-
 ...GlobalLoggingManipulationIntegrationTest.groovy |    5 +-
 .../SamplesToolingApiIntegrationTest.groovy        |   23 +-
 .../ToolingApiClasspathIntegrationTest.groovy      |   35 +
 .../tooling/ToolingApiIntegrationTest.groovy       |   69 +-
 .../tooling/ToolingApiRemoteIntegrationTest.groovy |    4 +-
 .../integtests/tooling/fixture/ToolingApi.groovy   |   21 +-
 .../ToolingApiCompatibilitySuiteRunner.groovy      |   27 +-
 .../fixture/ToolingApiDistributionResolver.groovy  |   30 +-
 .../tooling/fixture/ToolingApiSpecification.groovy |   27 +-
 .../ToolingApiEclipseModelCrossVersionSpec.groovy  |   15 +-
 ...piEclipseLinkedResourcesCrossVersionSpec.groovy |    4 +-
 ...ngApiEclipseMinimalModelCrossVersionSpec.groovy |    9 +-
 ...EclipseModelWithFlatRepoCrossVersionSpec.groovy |    2 -
 ...ToolingApiBuildExecutionCrossVersionSpec.groovy |   18 +-
 ...ildableEclipseModelFixesCrossVersionSpec.groovy |    7 +-
 .../ToolingApiEclipseModelCrossVersionSpec.groovy  |    6 +-
 .../ToolingApiGradleProjectCrossVersionSpec.groovy |   10 +-
 ...orsProjectCustomizationsCrossVersionSpec.groovy |   17 +-
 .../m5/ToolingApiIdeaModelCrossVersionSpec.groovy  |   68 +-
 .../m5/ToolingApiModelCrossVersionSpec.groovy      |    4 +-
 ...ReceivingStandardStreamsCrossVersionSpec.groovy |    4 +-
 .../BuildEnvironmentModelCrossVersionSpec.groovy   |    8 +-
 .../ConsumingStandardInputCrossVersionSpec.groovy  |   10 +-
 .../m8/GradlePropertiesCrossVersionSpec.groovy     |    8 +-
 .../m8/JavaConfigurabilityCrossVersionSpec.groovy  |    6 +-
 .../ToolingApiEclipseModelCrossVersionSpec.groovy  |    1 -
 .../m8/ToolingApiLoggingCrossVersionSpec.groovy    |    4 +-
 ...sionOnlyBuildEnvironmentCrossVersionSpec.groovy |    2 +-
 .../M9JavaConfigurabilityCrossVersionSpec.groovy   |   22 +-
 ...singCommandLineArgumentsCrossVersionSpec.groovy |   22 +-
 .../DependencyMetaDataCrossVersionSpec.groovy      |    8 +-
 .../r12rc1/BuildModelCrossVersionSpec.groovy       |    3 +-
 .../ProjectOutcomesModuleCrossVersionSpec.groovy   |    6 +-
 ...ApiInitScriptCrossVersionIntegrationTest.groovy |   88 ++
 .../consumer/DistributionFactoryTest.groovy        |   16 +-
 .../consumer/SynchronizedLoggingTest.groovy        |    2 +-
 .../DefaultToolingImplementationLoaderTest.groovy  |    6 +-
 ...chronizedToolingImplementationLoaderTest.groovy |   10 +-
 subprojects/tooling-api/tooling-api.gradle         |   60 +-
 .../integtests/FavoritesIntegrationTest.java       |    4 +-
 .../integtests/LiveOutputIntegrationTest.groovy    |   32 +-
 ...projectProjectAndTaskListIntegrationTest.groovy |   14 +-
 .../WrapperCrossVersionIntegrationTest.groovy      |   12 +-
 .../WrapperProjectIntegrationTest.groovy           |   10 +-
 .../groovy/org/gradle/wrapper/DownloadTest.groovy  |    6 +-
 .../groovy/org/gradle/wrapper/InstallTest.groovy   |   11 +-
 .../wrapper/SystemPropertiesHandlerTest.groovy     |    4 +-
 .../org/gradle/wrapper/WrapperExecutorTest.groovy  |    8 +-
 version.txt                                        |    1 +
 1619 files changed, 57252 insertions(+), 34370 deletions(-)

diff --git a/build.gradle b/build.gradle
index 504fade..d5bca86 100644
--- a/build.gradle
+++ b/build.gradle
@@ -25,28 +25,33 @@ archivesBaseName = 'gradle'
 extensions.buildTypes = new BuildTypes(project)
 
 buildTypes {
+    sanityCheck "classes", "doc:checkstyleApi", "codeQuality", "docs:check"
+
     // The minimum to be run before check-in
-    preCommitBuild "doc:checkstyleApi", "codeQuality", "classes", "test", noDistTests: true
-    quickCheck "doc:checkstyleApi", "codeQuality", "classes", "test", noDistTests: true
+    preCommitBuild "doc:checkstyleApi", "docs:check", "codeQuality", "classes", "test", noDistTests: true
+    quickCheck "doc:checkstyleApi", "docs:check", "codeQuality", "classes", "test", noDistTests: true
 
     // A full (in-process) test
     developerBuild "check"
 
     // Used by the first phase of the build pipeline
-    quickTest "test", "integTest", noDistTests: true
+    quickTest "test", "integTest", noDistTests: true, noDocsTests: true
 
     // Used for builds to test the code on certain platforms
-    platformTest "test", "forkingIntegTest", noDistTests: true, useIncomingDistributions: true
+    platformTest "test", "forkingIntegTest", noDistTests: true, noDocsTests: true, useIncomingDistributions: true
 
     // Tests using the daemon mode
-    daemonTest "daemonIntegTest", noDistTests: true, useIncomingDistributions: true
+    daemonTest "daemonIntegTest", noDistTests: true, noDocsTests: true, useIncomingDistributions: true
 
     // Run the integration tests using the parallel executer
-    parallelTest "parallelIntegTest", noDistTests: true, useIncomingDistributions: true
+    parallelTest "parallelIntegTest", noDistTests: true, noDocsTests: true, useIncomingDistributions: true
 
     // Run the performance tests
     performanceTest "performance:integTest", useIncomingDistributions: true
 
+    // Run the performance tests
+    localPerformanceTest "performance:integTest"
+
     // Used for cross version tests on CI
     crossVersionTest "forkingIntegTest", crossVersionTestsOnly: "", testAllVersions: "", noDistTests: true, useIncomingDistributions: true
 
@@ -54,7 +59,7 @@ buildTypes {
     packageBuild "verifyIsProductionBuildEnvironment", "clean", "buildDists", "distributions:integTest"
 
     // Used to build production distros and smoke test them
-    promotionBuild "verifyIsProductionBuildEnvironment", "clean", "buildDists", "distributions:integTest", "uploadArchives"
+    promotionBuild "verifyIsProductionBuildEnvironment", "clean", "docs:check", "buildDists", "distributions:integTest", "uploadArchives"
 }
 
 ext {
@@ -76,7 +81,7 @@ ext {
     }
 
     internalProjects = subprojects.findAll { it.name.startsWith("internal") || it.name in ["integTest", "distributions"] }
-    groovyProjects = subprojects.findAll { !(it.name in ["docs"]) }
+    groovyProjects = subprojects
     publicGroovyProjects = groovyProjects - internalProjects
     publishedProjects = [project(':core'), project(':toolingApi'), project(':wrapper'), project(':baseServices'), project(':messaging')]
     pluginProjects = [
@@ -142,6 +147,7 @@ configurations {
 
 dependencies {
     runtime project(':launcher')
+    runtime project(':wrapper')
     plugins pluginProjects
     plugins project(':coreImpl')
 }
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 2d466be..6973939 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -30,8 +30,8 @@ dependencies {
     compile 'com.google.guava:guava:11.0.2 at jar'
     compile 'commons-lang:commons-lang:2.6 at jar'
     groovy localGroovy()
-    testCompile 'junit:junit:4.10 at jar'
-    testCompile 'org.spockframework:spock-core:0.6-groovy-1.8 at jar', 'cglib:cglib-nodep:2.2', 'org.objenesis:objenesis:1.2'
+    testCompile 'junit:junit:4.11 at jar'
+    testCompile 'org.spockframework:spock-core:0.7-groovy-1.8 at jar', 'cglib:cglib-nodep:2.2', 'org.objenesis:objenesis:1.2'
 
     compile "org.pegdown:pegdown:1.1.0"
     compile "org.jsoup:jsoup:1.6.3"
diff --git a/buildSrc/src/main/groovy/org/gradle/build/BuildTypes.groovy b/buildSrc/src/main/groovy/org/gradle/build/BuildTypes.groovy
index 4490268..aa65af4 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/BuildTypes.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/BuildTypes.groovy
@@ -41,12 +41,21 @@ class BuildTypes {
         if (args.first() instanceof Map) {
             properties.putAll(args.remove(0))
         }
-        def tasks = args*.toString()
+        def tasks = []
+        def configure = {}
 
-        register(name, tasks, properties)
+        args.each {
+            if (it instanceof Closure) {
+                configure = it
+            } else {
+                tasks << it.toString()
+            }
+        }
+
+        register(name, tasks, properties, configure)
     }
 
-    private register(name, tasks, projectProperties) {
+    private register(name, tasks, projectProperties, configure) {
         project.task(name) {
             group = "Build Type"
             def abbreviation = name[0] + name[1..-1].replaceAll("[a-z]", "")
@@ -66,6 +75,7 @@ class BuildTypes {
                     }
                     project.properties."$k" = v
                 }
+                project.configure(project.gradle.startParameter, configure)
             }
 
             doFirst {
diff --git a/buildSrc/src/main/groovy/org/gradle/build/JarJarJar.groovy b/buildSrc/src/main/groovy/org/gradle/build/JarJarJar.groovy
new file mode 100644
index 0000000..0af4c56
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/JarJarJar.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.build
+
+import org.gradle.api.tasks.bundling.Jar
+import org.gradle.api.*
+import org.gradle.api.file.*
+import org.gradle.api.tasks.*
+import com.tonicsystems.jarjar.Main as JarJarMain
+
+/*
+* a Jar task that performs JarJar repackaging after archive is created
+* */
+class JarJarJar extends Jar {
+    @Input def rules = [:]
+    @Input def keeps = []
+
+    public JarJarJar() {
+        doLast {
+            executeJarJar();
+        }
+        doLast {
+            fixEmptyFoldersWithJarJar()
+        }
+    }
+
+    void executeJarJar() {
+        File tempRuleFile = new File(getTemporaryDir(), "jarjar.rules.txt")
+        writeRuleFile(tempRuleFile)
+        JarJarMain.main("process", tempRuleFile.absolutePath, getArchivePath().absolutePath, getArchivePath().absolutePath)
+    }
+
+    void fixEmptyFoldersWithJarJar() {
+        def withNoEmptyDirs = "${getTemporaryDir()}/withNoEmptyDirs"
+        project.copy{
+            from project.zipTree(getArchivePath().absolutePath)
+            into withNoEmptyDirs
+            includeEmptyDirs = false
+        }
+        project.ant {
+            zip(destfile: getArchivePath(), update: false) {
+                fileset(dir: withNoEmptyDirs)
+            }
+        }
+    }
+
+    void rule(String pattern, String result) {
+        rules[pattern] = result
+    }
+
+    void keep(String pattern) {
+        keeps << pattern
+    }
+
+    private void writeRuleFile(File ruleFile) {
+        ruleFile.withPrintWriter { writer ->
+            rules.each {pattern, result ->
+                writer.println("rule ${pattern} ${result}")
+            }
+            keeps.each {pattern ->
+                writer.println("keep ${pattern}")
+            }
+        }
+    }
+}
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
index c948e65..1951392 100644
--- a/config/checkstyle/suppressions.xml
+++ b/config/checkstyle/suppressions.xml
@@ -20,7 +20,10 @@
               files=".*[/\\]subprojects[/\\]scala[/\\]src[/\\]main[/\\]groovy[/\\]org[/\\]gradle[/\\]api[/\\]tasks[/\\][^/\\]+"/>
     <suppress checks="JavadocPackage"
               files=".*[/\\]subprojects[/\\]base-services[/\\]src[/\\]main[/\\]java[/\\]org[/\\]gradle[/\\]api[/\\][^/\\]+"/>
-
+    <suppress checks="JavadocPackage"
+              files=".*[/\\]subprojects[/\\]base-services-groovy[/\\]src[/\\]main[/\\]groovy[/\\]org[/\\]gradle[/\\]api[/\\][^/\\]+"/>
+    <suppress checks="JavadocPackage"
+              files=".*[/\\]subprojects[/\\]base-services-groovy[/\\]src[/\\]main[/\\]groovy[/\\]org[/\\]gradle[/\\]api[/\\]specs[/\\][^/\\]+"/>
     <!-- Don't require api docs for projects only used internally -->
     <suppress checks="Javadoc.*"
               files=".*[/\\]subprojects[/\\]internal-.+[/\\]src[/\\]main[/\\].+"/>
diff --git a/gradle/buildReceipt.gradle b/gradle/buildReceipt.gradle
index 9ead861..893ce2e 100644
--- a/gradle/buildReceipt.gradle
+++ b/gradle/buildReceipt.gradle
@@ -109,6 +109,9 @@ task createBuildReceipt(dependsOn: determineCommitId) {
         def data = [
                 commitId:  determineCommitId.commitId,
                 versionNumber: version,
+                versionBase: versionBase,
+                isSnapshot: isSnapshot,
+                rcNumber: rcNumber == null ? "" : rcNumber,
                 buildTimestamp: buildTimestamp,
                 username: System.properties["user.name"],
                 hostname: hostName,
diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle
index b6ef1fe..f269dbb 100644
--- a/gradle/dependencies.gradle
+++ b/gradle/dependencies.gradle
@@ -37,18 +37,18 @@ libraries.jcip = "net.jcip:jcip-annotations:1.0 at jar"
 libraries.inject = dependencies.module('javax.inject:javax.inject:1')
 
 // Logging
-libraries.slf4j_api = 'org.slf4j:slf4j-api:1.6.6 at jar'
-libraries.jcl_to_slf4j = dependencies.module('org.slf4j:jcl-over-slf4j:1.6.6') {
+libraries.slf4j_api = 'org.slf4j:slf4j-api:1.7.2 at jar'
+libraries.jcl_to_slf4j = dependencies.module('org.slf4j:jcl-over-slf4j:1.7.2') {
     dependency libraries.slf4j_api
 }
-libraries.jul_to_slf4j = dependencies.module('org.slf4j:jul-to-slf4j:1.6.6') {
+libraries.jul_to_slf4j = dependencies.module('org.slf4j:jul-to-slf4j:1.7.2') {
     dependency libraries.slf4j_api
 }
-libraries.log4j_to_slf4j = dependencies.module('org.slf4j:log4j-over-slf4j:1.6.6') {
+libraries.log4j_to_slf4j = dependencies.module('org.slf4j:log4j-over-slf4j:1.7.2') {
     dependency libraries.slf4j_api
 }
-libraries.logback_core = 'ch.qos.logback:logback-core:1.0.6 at jar'
-libraries.logback_classic = dependencies.module('ch.qos.logback:logback-classic:1.0.6') {
+libraries.logback_core = 'ch.qos.logback:logback-core:1.0.9 at jar'
+libraries.logback_classic = dependencies.module('ch.qos.logback:logback-classic:1.0.9') {
     dependency libraries.logback_core
     dependency libraries.slf4j_api
 }
@@ -64,11 +64,11 @@ libraries.jetty = dependencies.module("org.mortbay.jetty:jetty:6.1.25") {
     dependency libraries.servlet_api
 }
 
-libraries.commons_httpclient = dependencies.module('org.apache.httpcomponents:httpclient:4.2.1') {
-    dependency "org.apache.httpcomponents:httpcore:4.2.1 at jar"
+libraries.commons_httpclient = dependencies.module('org.apache.httpcomponents:httpclient:4.2.2') {
+    dependency "org.apache.httpcomponents:httpcore:4.2.2 at jar"
     dependency libraries.jcl_to_slf4j
     dependency "commons-codec:commons-codec:1.6 at jar"
-    dependency "org.samba.jcifs:jcifs:1.3.17"
+    dependency "org.samba.jcifs:jcifs:1.3.17 at jar"
 }
 
 libraries.maven_ant_tasks = dependencies.module("org.apache.maven:maven-ant-tasks:2.1.3") {
@@ -76,21 +76,22 @@ libraries.maven_ant_tasks = dependencies.module("org.apache.maven:maven-ant-task
 }
 
 libraries += [
-        ant_junit: 'org.apache.ant:ant-junit:1.8.4 at jar',
         ant_antlr: 'org.apache.ant:ant-antlr:1.8.4 at jar',
         antlr: 'antlr:antlr:2.7.7 at jar',
         dom4j: 'dom4j:dom4j:1.6.1 at jar',
         guava: 'com.google.guava:guava:11.0.2 at jar',
-        jsr305: 'com.google.code.findbugs:jsr305:1.3.9',
+        jsr305: 'com.google.code.findbugs:jsr305:1.3.9 at jar',
         groovy: 'org.codehaus.groovy:groovy-all:1.8.6 at jar',
         jaxen: 'jaxen:jaxen:1.1 at jar',
-        jcip: "net.jcip:jcip-annotations:1.0",
+        jcip: "net.jcip:jcip-annotations:1.0 at jar",
         jna: 'net.java.dev.jna:jna:3.2.7 at jar',
-        junit: 'junit:junit:4.10',
+        junit: 'junit:junit:4.11 at jar',
         xmlunit: 'xmlunit:xmlunit:1.3',
         nekohtml: 'net.sourceforge.nekohtml:nekohtml:1.9.14',
         xbean: 'org.apache.xbean:xbean-reflect:3.4 at jar', //required by maven3 classes
-        nativePlatform: 'net.rubygrapefruit:native-platform:0.2'
+        nativePlatform: 'net.rubygrapefruit:native-platform:0.2',
+        xerces: "xerces:xercesImpl:2.9.1",
+        objenesis: 'org.objenesis:objenesis:1.2 at jar'
 ]
 
 libraries.maven3 = dependencies.module("org.apache.maven:maven-core:3.0.4") {
@@ -131,15 +132,15 @@ libraries.maven3 = dependencies.module("org.apache.maven:maven-core:3.0.4") {
 libraries.spock = [
     'org.spockframework:spock-core:0.7-groovy-1.8 at jar',
     libraries.groovy,
-    'org.objenesis:objenesis:1.2',
+    libraries.objenesis,
     'cglib:cglib-nodep:2.2.2'
 ]
 libraries.jmock = [
     'org.jmock:jmock:2.5.1',
-    'org.hamcrest:hamcrest-core:1.1',
-    'org.hamcrest:hamcrest-library:1.1',
+    'org.hamcrest:hamcrest-core:1.3',
+    'org.hamcrest:hamcrest-library:1.3',
     dependencies.create('org.jmock:jmock-junit4:2.5.1') { exclude group: 'junit', module: 'junit-dep' }, //junit-dep pulls old definitions of core junit types.
     'org.jmock:jmock-legacy:2.5.1',
-    'org.objenesis:objenesis:1.2',
+    libraries.objenesis,
     'cglib:cglib-nodep:2.2'
 ]
diff --git a/gradle/groovyProject.gradle b/gradle/groovyProject.gradle
index 76d6952..f69306b 100644
--- a/gradle/groovyProject.gradle
+++ b/gradle/groovyProject.gradle
@@ -34,7 +34,6 @@ sourceSets {
     main.output.dir generatedResourcesDir, builtBy: classpathManifest
 }
 
-compileTasks.all { options.useAnt = false }
 testTasks.all { task ->
     maxParallelForks = rootProject.maxParallelForks
     if (isCiServer) {
@@ -46,8 +45,8 @@ testTasks.all { task ->
 
 jarTasks.all { jar ->
     jar.manifest.mainAttributes(
-        (Attributes.Name.IMPLEMENTATION_TITLE.toString()): 'Gradle',
-        (Attributes.Name.IMPLEMENTATION_VERSION.toString()): version,
+            (Attributes.Name.IMPLEMENTATION_TITLE.toString()): 'Gradle',
+            (Attributes.Name.IMPLEMENTATION_VERSION.toString()): version,
     )
 }
 
@@ -97,4 +96,5 @@ class ClasspathManifest extends DefaultTask {
     def generate() {
         manifestFile.withOutputStream { properties.save(it, 'module definition') }
     }
-}
\ No newline at end of file
+}
+ext.ClasspathManifest = ClasspathManifest
\ No newline at end of file
diff --git a/gradle/idea.gradle b/gradle/idea.gradle
index 906ca03..ab88ec9 100644
--- a/gradle/idea.gradle
+++ b/gradle/idea.gradle
@@ -83,7 +83,7 @@ idea {
                 if (aslCopyright == null) {
                   copyrightManager.append(new XmlParser().parseText('''
                       <copyright>
-                          <option name="notice" value="Copyright 2012 the original author or authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an &quot [...]
+                          <option name="notice" value="Copyright 2013 the original author or authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an &quot [...]
                           <option name="keyword" value="Copyright" />
                           <option name="allowReplaceKeyword" value="" />
                           <option name="myName" value="ASL2" />
@@ -210,7 +210,18 @@ idea {
 
         Node vmParameters = runConfig.option.find { it.'@name' == 'VM_PARAMETERS' }
 
-        vmParameters.'@value' = "\"-DintegTest.gradleHomeDir=${intTestImage.destinationDir}\" -ea -Dorg.gradle.integtest.executer=embedded -XX:MaxPermSize=512m -Xmx512m"
+        def docsProject = project(":docs")
+        def defaultTestVmParams = [
+                "-Dorg.gradle.docs.releasenotes.source=${docsProject.releaseNotesMarkdown.source.singleFile}",
+                "-Dorg.gradle.docs.releasenotes.rendered=${new File(docsProject.releaseNotes.destinationDir, docsProject.releaseNotes.fileName)}",
+                "-DintegTest.gradleHomeDir=${intTestImage.destinationDir}",
+                "-Dorg.gradle.integtest.executer=embedded",
+                "-ea",
+                "-XX:MaxPermSize=512m",
+                "-Xmx512m"
+        ]
+
+        vmParameters.'@value' = defaultTestVmParams.collect { it.contains(" ") ? "\"$it\"" : it }.join(" ")
 
         // Add an application configuration
         runManagerConfig.'@selected' = 'Application.Gradle'
diff --git a/gradle/integTest.gradle b/gradle/integTest.gradle
index aa51234..d5b36ca 100644
--- a/gradle/integTest.gradle
+++ b/gradle/integTest.gradle
@@ -63,12 +63,8 @@ check.dependsOn(integTest)
 
 ['embedded', 'forking', 'daemon', 'embeddedDaemon', 'parallel'].each { mode ->
     def taskName = "${mode}IntegTest"
-    tasks.addRule(taskName) { name ->
-        if (name == taskName) { 
-            tasks.add(taskName, Test).configure {
-                systemProperties['org.gradle.integtest.executer'] = mode
-            }
-        }
+    tasks.add(taskName, Test).configure {
+        systemProperties['org.gradle.integtest.executer'] = mode
     }
 }
 
diff --git a/gradle/publish.gradle b/gradle/publish.gradle
index e6265f2..fc23d30 100644
--- a/gradle/publish.gradle
+++ b/gradle/publish.gradle
@@ -6,6 +6,9 @@ configurations {
     compile {
         extendsFrom publishCompile
     }
+    publishCompileWithProjectJar {
+        extendsFrom publishCompile
+    }
 }
 
 task sourceJar(type: Jar) {
@@ -35,17 +38,19 @@ task generatePom {
             pom.version = version
             pom.writeTo(pomFile)
         }
-
     }
 }
 
 artifacts {
+    publishCompileWithProjectJar jar
     publishRuntime jar
     publishRuntime sourceJar
     publishRuntime new org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact(archivesBaseName, 'pom', 'pom', null, new Date(), generatePom.pomFile, generatePom)
 }
 
 uploadArchives { task ->
+    onlyIf { !project.hasProperty("noUpload") }
+
     gradle.taskGraph.whenReady { graph ->
         if (graph.hasTask(task)) {
             // check properties defined and fail early
diff --git a/gradle/testFixtures.gradle b/gradle/testFixtures.gradle
index f7937a1..ffc5fc5 100644
--- a/gradle/testFixtures.gradle
+++ b/gradle/testFixtures.gradle
@@ -9,13 +9,15 @@
 apply plugin: 'java'
 
 configurations {
+    outputDirs
+
     testFixturesCompile.extendsFrom compile
     testFixturesRuntime.extendsFrom runtime, testFixturesCompile
         
     // Expose configurations that include the test fixture classes for clients to use
-    testFixturesUsageCompile.extendsFrom testFixturesCompile
+    testFixturesUsageCompile.extendsFrom testFixturesCompile, outputDirs
     testFixturesUsageRuntime.extendsFrom testFixturesRuntime
-    
+
     // Assume that the project wants to use the fixtures for its tests
     testCompile.extendsFrom testFixturesUsageCompile
     testRuntime.extendsFrom testFixturesUsageRuntime
@@ -29,26 +31,32 @@ sourceSets {
 }
 
 dependencies {
-    testFixturesUsageCompile sourceSets.testFixtures.output, sourceSets.main.output
+    outputDirs sourceSets.testFixtures.output, sourceSets.main.output
     testFixturesCompile libraries.junit, libraries.jmock, libraries.spock
 }
 
-plugins.withType(org.gradle.plugins.ide.idea.IdeaPlugin) { // lazy as plugin not applied yet
+plugins.withType(org.gradle.plugins.ide.idea.IdeaPlugin) {
     idea {
         module {
             testSourceDirs += sourceSets.testFixtures.groovy.srcDirs
             testSourceDirs += sourceSets.testFixtures.resources.srcDirs
             scopes.TEST.plus.add(configurations.testFixturesCompile)
             scopes.TEST.plus.add(configurations.testFixturesRuntime)
+
+            //avoiding the certain output directories from the classpath in IDEA
+            scopes.TEST.minus.add(configurations.outputDirs)
         }
     }
 }
 
-plugins.withType(org.gradle.plugins.ide.eclipse.EclipsePlugin) { // lazy as plugin not applied yet
+plugins.withType(org.gradle.plugins.ide.eclipse.EclipsePlugin) {
     eclipse {
         classpath {
             plusConfigurations.add(configurations.testFixturesCompile)
             plusConfigurations.add(configurations.testFixturesRuntime)
+
+            //avoiding the certain output directories from the classpath in Eclipse
+            minusConfigurations.add(configurations.outputDirs)
         }
     }
 }
diff --git a/gradle/versioning.gradle b/gradle/versioning.gradle
index 3f68ddd..cbc21d9 100644
--- a/gradle/versioning.gradle
+++ b/gradle/versioning.gradle
@@ -2,64 +2,53 @@ def timestampFormat = new java.text.SimpleDateFormat('yyyyMMddHHmmssZ')
 timestampFormat.timeZone = TimeZone.getTimeZone("UTC")
 
 if (incomingDistributionsBuildReceipt) {
-    def incomingVersion = incomingDistributionsBuildReceipt.versionNumber
-    def match = incomingVersion =~ /(.+)-(\d{14}[+-]\d{4})/
-    if (!match) {
-        throw new GradleException("Can't extract version number from incoming distribution build receipt version number: $incomingVersion")
-    }
-
-    version = match[0][1]
-    ext.buildTimestamp = match[0][2]
-}
-
-if (project.hasProperty("buildTimestamp")) {
-    ext.buildTime = timestampFormat.parse(buildTimestamp)
+    ext.versionBase = incomingDistributionsBuildReceipt.versionBase
+    ext.buildTimestamp = incomingDistributionsBuildReceipt.buildTimestamp
 } else {
-    File timestampFile = file("$buildDir/timestamp.txt")
-    if (timestampFile.isFile()) {
-        boolean uptodate = true
-        def modified = timestampFile.lastModified()
-        project(':core').fileTree('src/main').visit {fte ->
-            if (fte.file.isFile() && fte.lastModified > modified) {
-                uptodate = false
-                fte.stopVisiting()
+    ext.versionBase = rootProject.file("version.txt").text.trim()
+
+    if (project.hasProperty("buildTimestamp")) {
+        ext.buildTime = timestampFormat.parse(buildTimestamp)
+    } else {
+        File timestampFile = file("$buildDir/timestamp.txt")
+        if (timestampFile.isFile()) {
+            boolean uptodate = true
+            def modified = timestampFile.lastModified()
+            project(':core').fileTree('src/main').visit {fte ->
+                if (fte.file.isFile() && fte.lastModified > modified) {
+                    uptodate = false
+                    fte.stopVisiting()
+                }
             }
+            if (!uptodate) {
+                timestampFile.setLastModified(new Date().time)
+            }
+        } else {
+            timestampFile.parentFile.mkdirs()
+            timestampFile.createNewFile()
         }
-        if (!uptodate) {
-            timestampFile.setLastModified(new Date().time)
-        }
-    } else {
-        timestampFile.parentFile.mkdirs()
-        timestampFile.createNewFile()
-    }
-
-    ext.buildTime = new Date(timestampFile.lastModified())
-    ext.buildTimestamp = timestampFormat.format(buildTime)
-}
-
-ext.isSnapshot = !project.hasProperty("notSnapshot")
 
-if (version == "unspecified") {
-    if (!isSnapshot) {
-        throw new InvalidUserDataException("Cannot specify 'notSnapshot' without specifying 'version'")
+        ext.buildTime = new Date(timestampFile.lastModified())
+        ext.buildTimestamp = timestampFormat.format(buildTime)
     }
+}
 
-    // 0.0 means the version is non symbolic.
-    // We have to use something that looks like a real version number because there are a few places
-    // where this value is validated, e.g. the wrapper stuff. We should fix this at some point so we can do
-    // things like use build numbers or commit IDs.
-    //
-    // version = "0.0"
+ext.rcNumber = project.hasProperty("rcNumber") ? project.rcNumber.toInteger() : null
+ext.finalRelease = project.hasProperty("finalRelease")
+if (rcNumber != null && finalRelease) {
+    throw new InvalidUserDataException("Cannot set rcNumber and finalRelease at the same time")
+}
 
-    // Using 0.0 causes issues with the Tooling API. It's something to do with older versions of Gradle not
-    // understanding the 0.0 version number scheme. For the time being, we are going to hardcode the version.
-    version = "1.3"
+version = versionBase
 
-    ext.isSymbolicVersion = false
+ext.isSnapshot = false
+if (finalRelease) {
+    // use version base
+} else if (rcNumber != null) {
+    version += "-rc-$rcNumber"
 } else {
-    ext.isSymbolicVersion = true
+    isSnapshot = true
+    version += "-$buildTimestamp"
 }
 
-if (isSnapshot) {
-    version += "-$buildTimestamp"
-}
\ No newline at end of file
+println "Gradle version: $version"
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index ec6b49c..609bad0 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sun Sep 02 13:16:06 EST 2012
+#Thu Jan 03 17:29:33 GMT 2013
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=http\://services.gradle.org/distributions-snapshots/gradle-1.3-20121109123943+0000-bin.zip
+distributionUrl=http\://services.gradle.org/distributions-snapshots/gradle-1.4-20130116153842+0000-bin.zip
\ No newline at end of file
diff --git a/gradlew b/gradlew
index 39e1569..e329155 100755
--- a/gradlew
+++ b/gradlew
@@ -61,9 +61,9 @@ while [ -h "$PRG" ] ; do
     fi
 done
 SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/"
+cd "`dirname \"$PRG\"`/" >&-
 APP_HOME="`pwd -P`"
-cd "$SAVED"
+cd "$SAVED" >&-
 
 CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
 
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactory.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactory.groovy
index 4905ab0..8c5b687 100755
--- a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactory.groovy
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactory.groovy
@@ -20,7 +20,6 @@ import org.gradle.api.JavaVersion
 import org.gradle.api.internal.ProcessOperations
 import org.gradle.api.plugins.announce.AnnouncePluginExtension
 import org.gradle.api.plugins.announce.Announcer
-import org.gradle.internal.jvm.Jvm
 import org.gradle.internal.os.OperatingSystem
 
 /**
@@ -61,10 +60,14 @@ class DefaultAnnouncerFactory implements AnnouncerFactory {
             case "snarl":
                 return new Snarl(iconProvider)
             case "growl":
-                if (JavaVersion.current().java6Compatible && Jvm.current().supportsAppleScript) {
+                if (JavaVersion.current().java6Compatible) {
                     try {
                         return getClass().getClassLoader().loadClass("org.gradle.api.plugins.announce.internal.jdk6.AppleScriptBackedGrowlAnnouncer").newInstance(iconProvider)
-                    } catch (ClassNotFoundException e) {
+                    }
+                    catch (AnnouncerUnavailableException e) {
+                        // Ignore and fall back to growl notify
+                    }
+                    catch (ClassNotFoundException e) {
                         // Ignore and fall back to growl notify
                     }
                 }
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/jdk6/AppleScriptBackedGrowlAnnouncer.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/jdk6/AppleScriptBackedGrowlAnnouncer.groovy
index b9fe6d0..fd13996 100644
--- a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/jdk6/AppleScriptBackedGrowlAnnouncer.groovy
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/jdk6/AppleScriptBackedGrowlAnnouncer.groovy
@@ -15,23 +15,31 @@
  */
 package org.gradle.api.plugins.announce.internal.jdk6
 
-import javax.script.ScriptEngine
-import javax.script.ScriptEngineManager
+import org.gradle.api.plugins.announce.internal.AnnouncerUnavailableException
 import org.gradle.api.plugins.announce.internal.Growl
 import org.gradle.api.plugins.announce.internal.IconProvider
-import org.gradle.api.plugins.announce.internal.AnnouncerUnavailableException
+
+import javax.script.ScriptEngine
+import javax.script.ScriptEngineManager
 
 class AppleScriptBackedGrowlAnnouncer extends Growl {
     private final IconProvider iconProvider
+    private final ScriptEngine engine;
 
     AppleScriptBackedGrowlAnnouncer(IconProvider iconProvider) {
-        this.iconProvider = iconProvider
+        this.iconProvider = iconProvider;
+        ScriptEngineManager mgr = new ScriptEngineManager();
+
+        engine = mgr.getEngineByName("AppleScript");
+        if (engine == null) {
+            engine = mgr.getEngineByName("AppleScriptEngine");
+        }
+        if (engine == null) {
+            throw new AnnouncerUnavailableException("AppleScript engine not available on used JVM")
+        }
     }
 
     void send(String title, String message) {
-        ScriptEngineManager mgr = new ScriptEngineManager();
-        ScriptEngine engine = mgr.getEngineByName("AppleScript");
-
         String isRunning = """
 tell application "System Events"
 set isRunning to count of (every process whose bundle identifier is "com.Growl.GrowlHelperApp") > 0
@@ -44,8 +52,7 @@ return isRunning
         }
 
         def icon = iconProvider.getIcon(48, 48)
-        def iconDef = icon ? "image from location \"${icon.absolutePath}\"" : ""
-
+        def iconDef = icon ? "image from location ((POSIX file \"${icon.absolutePath}\") as string) as alias" : ""
         def script = """
 tell application id "com.Growl.GrowlHelperApp"
 register as application "Gradle" all notifications {"Build Notification"} default notifications {"Build Notification"}
diff --git a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/DefaultIconProviderTest.groovy b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/DefaultIconProviderTest.groovy
index cb4697a..299b016 100644
--- a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/DefaultIconProviderTest.groovy
+++ b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/DefaultIconProviderTest.groovy
@@ -16,18 +16,18 @@
 package org.gradle.api.plugins.announce.internal
 
 import org.gradle.api.internal.GradleDistributionLocator
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 class DefaultIconProviderTest extends Specification {
-    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final GradleDistributionLocator locator = Mock()
     final DefaultIconProvider provider = new DefaultIconProvider(locator)
     
     def "returns png file that matches specified dimensions exactly"() {
         given:
-        def homeDir = tmpDir.dir
+        def homeDir = tmpDir.testDirectory
         def pngFile = tmpDir.createFile("media/gradle-icon-48x18.png")
         _ * locator.gradleHome >> homeDir
 
@@ -45,7 +45,7 @@ class DefaultIconProviderTest extends Specification {
 
     def "returns null when no icon with exact dimension"() {
         given:
-        def homeDir = tmpDir.dir
+        def homeDir = tmpDir.testDirectory
         _ * locator.gradleHome >> homeDir
 
         expect:
diff --git a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrPlugin.java b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrPlugin.java
index b8b3cce..ee2a663 100644
--- a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrPlugin.java
+++ b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrPlugin.java
@@ -19,6 +19,7 @@ package org.gradle.api.plugins.antlr;
 import org.gradle.api.Action;
 import org.gradle.api.Plugin;
 import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.DefaultSourceSet;
@@ -27,6 +28,7 @@ import org.gradle.api.plugins.JavaPluginConvention;
 import org.gradle.api.plugins.antlr.internal.AntlrSourceVirtualDirectoryImpl;
 import org.gradle.api.tasks.SourceSet;
 
+import javax.inject.Inject;
 import java.io.File;
 import java.util.concurrent.Callable;
 
@@ -39,6 +41,12 @@ import static org.gradle.api.plugins.JavaPlugin.COMPILE_CONFIGURATION_NAME;
  */
 public class AntlrPlugin implements Plugin<ProjectInternal> {
     public static final String ANTLR_CONFIGURATION_NAME = "antlr";
+    private final FileResolver fileResolver;
+
+    @Inject
+    public AntlrPlugin(FileResolver fileResolver) {
+        this.fileResolver = fileResolver;
+    }
 
     public void apply(final ProjectInternal project) {
         project.getPlugins().apply(JavaPlugin.class);
@@ -55,7 +63,7 @@ public class AntlrPlugin implements Plugin<ProjectInternal> {
                         // for each source set we will:
                         // 1) Add a new 'antlr' virtual directory mapping
                         final AntlrSourceVirtualDirectoryImpl antlrDirectoryDelegate
-                                = new AntlrSourceVirtualDirectoryImpl(((DefaultSourceSet) sourceSet).getDisplayName(), project.getFileResolver());
+                                = new AntlrSourceVirtualDirectoryImpl(((DefaultSourceSet) sourceSet).getDisplayName(), fileResolver);
                         new DslObject(sourceSet).getConvention().getPlugins().put(
                                 AntlrSourceVirtualDirectory.NAME, antlrDirectoryDelegate);
                         final String srcDir = String.format("src/%s/antlr", sourceSet.getName());
diff --git a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrTask.java b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrTask.java
index a639551..452f97a 100644
--- a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrTask.java
+++ b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrTask.java
@@ -30,6 +30,7 @@ import org.gradle.api.tasks.TaskAction;
 import org.gradle.api.plugins.antlr.internal.MetadataExtracter;
 import org.gradle.api.plugins.antlr.internal.XRef;
 import org.gradle.api.plugins.antlr.internal.GenerationPlanBuilder;
+import org.gradle.util.GFileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -153,7 +154,7 @@ public class AntlrTask extends SourceTask {
             LOGGER.info("performing grammar generation [" + generationPlan.getId() + "]");
 
             //noinspection ResultOfMethodCallIgnored
-            generationPlan.getGenerationDirectory().mkdirs();
+            GFileUtils.mkdirs(generationPlan.getGenerationDirectory());
 
             ANTLR antlr = new ANTLR();
             antlr.setProject(getAnt().getAntProject());
diff --git a/subprojects/antlr/src/test/groovy/org/gradle/api/plugins/antlr/AntlrPluginTest.groovy b/subprojects/antlr/src/test/groovy/org/gradle/api/plugins/antlr/AntlrPluginTest.groovy
index f3a1bd8..b94619d 100644
--- a/subprojects/antlr/src/test/groovy/org/gradle/api/plugins/antlr/AntlrPluginTest.groovy
+++ b/subprojects/antlr/src/test/groovy/org/gradle/api/plugins/antlr/AntlrPluginTest.groovy
@@ -22,11 +22,10 @@ import org.gradle.util.HelperUtil
 
 class AntlrPluginTest extends Specification {
     private final Project project = HelperUtil.createRootProject()
-    private final AntlrPlugin plugin = new AntlrPlugin()
 
     def addsAntlrPropertiesToEachSourceSet() {
         when:
-        plugin.apply(project)
+        project.apply plugin: AntlrPlugin
 
         then:
         def main = project.sourceSets.main
@@ -45,7 +44,7 @@ class AntlrPluginTest extends Specification {
     
     def addsTaskForEachSourceSet() {
         when:
-        plugin.apply(project)
+        project.apply plugin: AntlrPlugin
 
         then:
         def main = project.tasks.generateGrammarSource
diff --git a/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/package-info.java b/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/package-info.java
deleted file mode 100644
index 5b78f5e..0000000
--- a/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/package-info.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * <p><b>Start Here:</b> Gradle's {@link org.gradle.api.Project} API, which is available from your build files. The
- * API used from your build files is made up of 2 main interfaces:</p>
- *
- * <ul>
- * <li>{@link org.gradle.api.Project}</li>
- * <li>{@link org.gradle.api.Task}</li>
- * </ul>
- */
-package org.gradle.api;
diff --git a/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/specs/package-info.java b/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/specs/package-info.java
deleted file mode 100644
index a5cc802..0000000
--- a/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/specs/package-info.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * Classes for defining general purpose criteria.
- */
-package org.gradle.api.specs;
\ No newline at end of file
diff --git a/subprojects/base-services-groovy/src/test/groovy/org/gradle/api/internal/ClosureBackedActionTest.groovy b/subprojects/base-services-groovy/src/test/groovy/org/gradle/api/internal/ClosureBackedActionTest.groovy
deleted file mode 100644
index 7db2ee9..0000000
--- a/subprojects/base-services-groovy/src/test/groovy/org/gradle/api/internal/ClosureBackedActionTest.groovy
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal
-
-import org.gradle.api.Action
-import org.gradle.api.InvalidActionClosureException
-import spock.lang.Specification
-
-class ClosureBackedActionTest extends Specification {
-
-    def "one arg closure is called"() {
-        given:
-        def called = false
-        def thing = "1"
-        def closure = {
-            called = true
-            assert it.is(thing)
-            assert delegate.is(thing)
-        }
-
-        when:
-        action(closure).execute(thing)
-
-        then:
-        called
-    }
-
-    def "zero arg closure is called"() {
-        given:
-        def called = false
-        def thing = "1"
-        def closure = { ->
-            called = true
-            assert delegate.is(thing)
-        }
-
-        when:
-        action(closure).execute(thing)
-
-        then:
-        called
-    }
-
-    def "closure with wrong param type is given"() {
-        given:
-        def closure = { Map m -> }
-        def arg = "1"
-
-        when:
-        action(closure).execute(arg)
-
-        then:
-        def e = thrown InvalidActionClosureException
-        e.closure.is(closure)
-        e.argument.is(arg)
-        e.message == "The closure '${closure.toString()}' is not valid as an action for argument '1'. It should accept no parameters, or one compatible with type 'java.lang.String'. It accepts (java.util.Map)."
-    }
-
-    def "closure with more than one param type is given"() {
-        given:
-        def closure = { Map m, List l -> }
-        def arg = "1"
-
-        when:
-        action(closure).execute(arg)
-
-        then:
-        def e = thrown InvalidActionClosureException
-        e.closure.is(closure)
-        e.argument.is(arg)
-        e.message == "The closure '${closure.toString()}' is not valid as an action for argument '1'. It should accept no parameters, or one compatible with type 'java.lang.String'. It accepts (java.util.Map, java.util.List)."
-    }
-
-    Action<?> action(Closure<?> c) {
-        new ClosureBackedAction(c)
-    }
-}
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/internal/Actions.java b/subprojects/base-services/src/main/java/org/gradle/api/internal/Actions.java
index d47dd99..20d3913 100644
--- a/subprojects/base-services/src/main/java/org/gradle/api/internal/Actions.java
+++ b/subprojects/base-services/src/main/java/org/gradle/api/internal/Actions.java
@@ -49,10 +49,21 @@ public abstract class Actions {
      * @param <T> The type of the object that action is for
      * @return The composite action.
      */
+    public static <T> Action<T> composite(Iterable<Action<? super T>> actions) {
+        return new CompositeAction<T>(actions);
+    }
+
+    /**
+     * Creates an action that will call each of the given actions in order.
+     *
+     * @param actions The actions to make a composite of.
+     * @param <T> The type of the object that action is for
+     * @return The composite action.
+     */
     public static <T> Action<T> composite(Action<? super T>... actions) {
         final List<Action<? super T>> actionsCopy = new ArrayList<Action<? super T>>(actions.length);
         Collections.addAll(actionsCopy, actions);
-        return new CompositeAction<T>(actionsCopy);
+        return composite(actionsCopy);
    }
 
     private static class CompositeAction<T> implements Action<T> {
@@ -67,6 +78,29 @@ public abstract class Actions {
                 action.execute(item);
             }
         }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            CompositeAction that = (CompositeAction) o;
+
+            if (!actions.equals(that.actions)) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            return actions.hashCode();
+        }
     }
 
     /**
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/CompositeStoppable.java b/subprojects/base-services/src/main/java/org/gradle/internal/CompositeStoppable.java
index 327850a..306962b 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/CompositeStoppable.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/CompositeStoppable.java
@@ -41,6 +41,14 @@ public class CompositeStoppable implements Stoppable {
         return new CompositeStoppable().add(elements);
     }
 
+    public static CompositeStoppable closeable(Closeable... elements) {
+        return new CompositeStoppable().add(elements);
+    }
+
+    public static CompositeStoppable closeable(Iterable<? extends Closeable> elements) {
+        return new CompositeStoppable().add(elements);
+    }
+
     public CompositeStoppable add(Iterable<?> elements) {
         for (Object element : elements) {
             this.elements.add(toStoppable(element));
@@ -139,4 +147,20 @@ public class CompositeStoppable implements Stoppable {
             throw UncheckedException.throwAsUncheckedException(failure);
         }
     }
-}
+
+    /**
+     * Similar to stop() but forwards the IOException from Closeables.
+     *
+     * @throws IOException
+     */
+    public void close() throws IOException {
+        try {
+            stop();
+        } catch (RuntimeException e) {
+            if (e.getCause() instanceof IOException) {
+                throw (IOException) e.getCause();
+            }
+            throw e;
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/LazyIterable.java b/subprojects/base-services/src/main/java/org/gradle/internal/LazyIterable.java
new file mode 100644
index 0000000..74ea26d
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/LazyIterable.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal;
+
+import java.util.Iterator;
+
+public class LazyIterable<T> implements Iterable<T> {
+
+    private final Factory<Iterable<T>> factory;
+
+    public LazyIterable(Factory<Iterable<T>> factory) {
+        this.factory = factory;
+    }
+
+    public Iterator<T> iterator() {
+        return factory.create().iterator();
+    }
+
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JavaInfo.java b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JavaInfo.java
index c52f229..cb4e842 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JavaInfo.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JavaInfo.java
@@ -61,6 +61,4 @@ public interface JavaInfo {
     File getToolsJar();
 
     Map<String, ?> getInheritableEnvironmentVariables(Map<String, ?> envVars);
-
-    boolean getSupportsAppleScript();
 }
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/jvm/Jvm.java b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/Jvm.java
index 5951a2a..db4b1f9 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/jvm/Jvm.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/Jvm.java
@@ -227,13 +227,6 @@ public class Jvm implements JavaInfo {
         return envVars;
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    public boolean getSupportsAppleScript() {
-        return false;
-    }
-
     public boolean isIbmJvm() {
         return false;
     }
@@ -294,13 +287,5 @@ public class Jvm implements JavaInfo {
             }
             return vars;
         }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public boolean getSupportsAppleScript() {
-            return true;
-        }
     }
 }
diff --git a/subprojects/base-services/src/main/java/org/gradle/util/CollectionUtils.java b/subprojects/base-services/src/main/java/org/gradle/util/CollectionUtils.java
index bcbb4a5..d2eff27 100644
--- a/subprojects/base-services/src/main/java/org/gradle/util/CollectionUtils.java
+++ b/subprojects/base-services/src/main/java/org/gradle/util/CollectionUtils.java
@@ -27,7 +27,7 @@ import static org.gradle.api.internal.Cast.cast;
 
 public abstract class CollectionUtils {
 
-    public static <T> T findFirst(Iterable<T> source, Spec<? super T> filter) {
+    public static <T> T findFirst(Iterable<? extends T> source, Spec<? super T> filter) {
         for (T item : source) {
             if (filter.isSatisfiedBy(item)) {
                 return item;
@@ -37,15 +37,21 @@ public abstract class CollectionUtils {
         return null;
     }
 
-    public static <T> Set<T> filter(Set<T> set, Spec<? super T> filter) {
+    public static <T> Set<T> filter(Set<? extends T> set, Spec<? super T> filter) {
         return filter(set, new LinkedHashSet<T>(), filter);
     }
 
-    public static <T> List<T> filter(List<T> list, Spec<? super T> filter) {
+    public static <T> List<T> filter(List<? extends T> list, Spec<? super T> filter) {
         return filter(list, new LinkedList<T>(), filter);
     }
 
-    public static <T, C extends Collection<T>> C filter(Iterable<T> source, C destination, Spec<? super T> filter) {
+    public static <T> List<T> sort(List<? extends T> list, Comparator<? super T> comparator) {
+        List<T> sortedList = new ArrayList<T>(list);
+        Collections.sort(sortedList, comparator);
+        return sortedList;
+    }
+
+    public static <T, C extends Collection<T>> C filter(Iterable<? extends T> source, C destination, Spec<? super T> filter) {
         for (T item : source) {
             if (filter.isSatisfiedBy(item)) {
                 destination.add(item);
@@ -68,11 +74,11 @@ public abstract class CollectionUtils {
         return destination;
     }
 
-    public static <R, I> R[] collectArray(I[] list, Class<R> newType, Transformer<R, I> transformer) {
+    public static <R, I> R[] collectArray(I[] list, Class<R> newType, Transformer<? extends R, ? super I> transformer) {
         return collectArray(list, (R[]) Array.newInstance(newType, list.length), transformer);
     }
 
-    public static <R, I> R[] collectArray(I[] list, R[] destination, Transformer<R, I> transformer) {
+    public static <R, I> R[] collectArray(I[] list, R[] destination, Transformer<? extends R, ? super I> transformer) {
         assert list.length <= destination.length;
         for (int i = 0; i < list.length; ++i) {
             destination[i] = transformer.transform(list[i]);
@@ -80,19 +86,19 @@ public abstract class CollectionUtils {
         return destination;
     }
 
-    public static <R, I> List<R> collect(List<? extends I> list, Transformer<R, ? super I> transformer) {
+    public static <R, I> List<R> collect(List<? extends I> list, Transformer<? extends R, ? super I> transformer) {
         return collect(list, new ArrayList<R>(list.size()), transformer);
     }
 
-    public static <R, I> List<R> collect(I[] list, Transformer<R, ? super I> transformer) {
+    public static <R, I> List<R> collect(I[] list, Transformer<? extends R, ? super I> transformer) {
         return collect(Arrays.asList(list), transformer);
     }
 
-    public static <R, I> Set<R> collect(Set<? extends I> set, Transformer<R, ? super I> transformer) {
+    public static <R, I> Set<R> collect(Set<? extends I> set, Transformer<? extends R, ? super I> transformer) {
         return collect(set, new HashSet<R>(), transformer);
     }
 
-    public static <R, I, C extends Collection<R>> C collect(Iterable<? extends I> source, C destination, Transformer<R, ? super I> transformer) {
+    public static <R, I, C extends Collection<R>> C collect(Iterable<? extends I> source, C destination, Transformer<? extends R, ? super I> transformer) {
         for (I item : source) {
             destination.add(transformer.transform(item));
         }
@@ -126,7 +132,7 @@ public abstract class CollectionUtils {
      * @param <T> The target type in the flattened list
      * @return A flattened list of the given things
      */
-    public static <T> List<? extends T> flattenToList(Class<T> type, Object... things) {
+    public static <T> List<T> flattenToList(Class<T> type, Object... things) {
         if (things == null) {
             return Collections.singletonList(null);
         } else if (things.length == 0) {
@@ -181,6 +187,21 @@ public abstract class CollectionUtils {
         return list;
     }
 
+    public static <T> Set<T> toSet(Iterable<? extends T> things) {
+        if (things == null) {
+            return new HashSet<T>(0);
+        }
+        if (things instanceof Set) {
+            return (Set<T>) things;
+        }
+
+        Set<T> set = new LinkedHashSet<T>();
+        for (T thing : things) {
+            set.add(thing);
+        }
+        return set;
+    }
+
     public static <E> List<E> compact(List<E> list) {
         boolean foundAtLeastOneNull = false;
         List<E> compacted = null;
@@ -212,7 +233,7 @@ public abstract class CollectionUtils {
         return stringize(source, new ArrayList<String>(source.size()));
     }
 
-    public static <E> boolean replace(List<E> list, Spec<? super E> filter, Transformer<E, ? super E> transformer) {
+    public static <E> boolean replace(List<E> list, Spec<? super E> filter, Transformer<? extends E, ? super E> transformer) {
         boolean replaced = false;
         int i = 0;
         for (E it : list) {
@@ -237,7 +258,7 @@ public abstract class CollectionUtils {
         return map;
     }
 
-    public static <T> boolean every(Iterable<T> things, Spec<? super T> predicate) {
+    public static <T> boolean every(Iterable<? extends T> things, Spec<? super T> predicate) {
         for (T thing : things) {
             if (!predicate.isSatisfiedBy(thing)) {
                 return false;
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/api/internal/ActionsTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/api/internal/ActionsTest.groovy
index cf0185d..a7fa944 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/api/internal/ActionsTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/api/internal/ActionsTest.groovy
@@ -60,6 +60,17 @@ class ActionsTest extends Specification {
         1 * action.execute(2)
     }
 
+    def "composite actions"() {
+        def actions = [Mock(Action), Mock(Action)]
+
+        when:
+        composite(actions).execute("foo")
+
+        then:
+        actions.each { 1 * it.execute("foo") }
+        0 * _._
+    }
+
     def "cast before"() {
         given:
         def action = Mock(Action)
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/api/internal/IoActionsTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/api/internal/IoActionsTest.groovy
index 3f82187..59572a4 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/api/internal/IoActionsTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/api/internal/IoActionsTest.groovy
@@ -18,7 +18,7 @@ package org.gradle.api.internal
 
 import org.gradle.api.Action
 import org.gradle.api.UncheckedIOException
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -27,7 +27,7 @@ import static org.gradle.api.internal.IoActions.writeFile
 
 class IoActionsTest extends Specification {
 
-    @Rule TemporaryFolder tmp
+    @Rule TestNameTestDirectoryProvider tmp
 
     def "can use file action to write to file"() {
         given:
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/LazyIterableTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/LazyIterableTest.groovy
new file mode 100644
index 0000000..717b5ad
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/LazyIterableTest.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal
+
+import spock.lang.Specification
+
+class LazyIterableTest extends Specification {
+
+    def "laziness"() {
+        given:
+        def l = [1]
+        def i = new LazyIterable({ l } as Factory)
+
+        expect:
+        i.collect() == [1]
+
+        when:
+        l << 2
+
+        then:
+        i.collect() == [1, 2]
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/AppleJvmTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/AppleJvmTest.groovy
index 53612a6..d10b027 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/AppleJvmTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/AppleJvmTest.groovy
@@ -17,14 +17,14 @@ package org.gradle.internal.jvm
 
 import org.gradle.internal.jvm.Jvm.AppleJvm
 import org.gradle.internal.os.OperatingSystem
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.SetSystemProperties
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
 import org.junit.Rule
 import spock.lang.Specification
 
 class AppleJvmTest extends Specification {
-    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     @Rule SetSystemProperties sysProp = new SetSystemProperties()
     OperatingSystem os = Mock(OperatingSystem)
 
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JvmTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JvmTest.groovy
index 8255b4f..91f81b2 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JvmTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JvmTest.groovy
@@ -17,14 +17,14 @@
 package org.gradle.internal.jvm
 
 import org.gradle.internal.os.OperatingSystem
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.SetSystemProperties
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
 import org.junit.Rule
 import spock.lang.Specification
 
 class JvmTest extends Specification {
-    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     @Rule SetSystemProperties sysProp = new SetSystemProperties()
     OperatingSystem os = Mock()
     OperatingSystem theOs = OperatingSystem.current()
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/os/OperatingSystemTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/os/OperatingSystemTest.groovy
index 86e8c9d..71e41da 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/os/OperatingSystemTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/os/OperatingSystemTest.groovy
@@ -15,14 +15,14 @@
  */
 package org.gradle.internal.os
 
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.SetSystemProperties
-import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
 
 class OperatingSystemTest extends Specification {
     @Rule SetSystemProperties systemProperties = new SetSystemProperties()
-    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
     def "uses os.name property to determine OS name"() {
         System.properties['os.name'] = 'GradleOS 1.0'
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy
index 472c548..4a7923f 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy
@@ -259,6 +259,15 @@ class CollectionUtilsTest extends Specification {
         e.message == "The 'action' cannot be null"
     }
 
+    def "to set"() {
+        expect:
+        toSet([1, 2, 3]) == [1, 2, 3] as Set
+        toSet([1, 2, 2, 3]) == [1, 2, 3] as Set
+        def set = [1] as Set
+        toSet(set).is(set)
+        toSet([]).empty
+    }
+
     Spec<?> spec(Closure c) {
         Specs.convertClosureToSpec(c)
     }
diff --git a/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec.groovy b/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec.groovy
index e25c10e..7836d3a 100644
--- a/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec.groovy
+++ b/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec.groovy
@@ -18,7 +18,7 @@ package org.gradle.api.plugins.buildcomparison.gradle
 
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.integtests.fixtures.WellBehavedPluginTest
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.jsoup.Jsoup
 import org.jsoup.nodes.Document
 import org.jsoup.nodes.Element
@@ -39,7 +39,7 @@ class BuildComparisonIntegrationSpec extends WellBehavedPluginTest {
     }
 
     def setup() {
-        executer.withForkingExecuter()
+        executer.requireGradleHome(true)
         applyPlugin()
     }
 
@@ -77,7 +77,7 @@ class BuildComparisonIntegrationSpec extends WellBehavedPluginTest {
         storedFile("target/_jar").list().toList() == ["testBuild.jar"]
 
         and: // old filestore not around
-        !testDir.list().any { it.startsWith(CompareGradleBuilds.TMP_FILESTORAGE_PREFIX) }
+        !testDirectory.list().any { it.startsWith(CompareGradleBuilds.TMP_FILESTORAGE_PREFIX) }
     }
 
     void failedBecauseNotIdentical() {
@@ -157,7 +157,7 @@ class BuildComparisonIntegrationSpec extends WellBehavedPluginTest {
         fails "compareGradleBuilds"
 
         and:
-        failure.assertHasCause("Builds must be executed with Gradle 1.0 or newer (source: 1.0-rc-1, target: ${distribution.version})")
+        failure.assertHasCause("Builds must be executed with Gradle 1.0 or newer (source: 1.0-rc-1, target: ${distribution.version.version})")
     }
 
     def "can ignore errors"() {
@@ -201,7 +201,7 @@ class BuildComparisonIntegrationSpec extends WellBehavedPluginTest {
         fails "compareGradleBuilds"
 
         and:
-        failure.assertHasCause("Builds must be executed with Gradle 1.0 or newer (source: ${distribution.version}, target: 1.0-rc-1)")
+        failure.assertHasCause("Builds must be executed with Gradle 1.0 or newer (source: ${distribution.version.version}, target: 1.0-rc-1)")
     }
 
     def "can handle artifact not existing on source side"() {
diff --git a/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/Pre12CompareGradleBuildsCrossVersionSpec.groovy b/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/Pre12CompareGradleBuildsCrossVersionSpec.groovy
index 07b0f6c..0d36e4b 100644
--- a/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/Pre12CompareGradleBuildsCrossVersionSpec.groovy
+++ b/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/Pre12CompareGradleBuildsCrossVersionSpec.groovy
@@ -17,10 +17,10 @@
 package org.gradle.api.plugins.buildcomparison.gradle
 
 import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
-import org.gradle.integtests.fixtures.ExecutionResult
-import org.gradle.integtests.fixtures.GradleExecuter
 import org.gradle.integtests.fixtures.TargetVersions
-import org.gradle.util.TestFile
+import org.gradle.integtests.fixtures.executer.ExecutionResult
+import org.gradle.integtests.fixtures.executer.GradleExecuter
+import org.gradle.test.fixtures.file.TestFile
 import org.jsoup.Jsoup
 import org.jsoup.nodes.Document
 
@@ -39,7 +39,7 @@ class Pre12CompareGradleBuildsCrossVersionSpec extends CrossVersionIntegrationSp
         buildFile << "apply plugin: 'java'"
         versionGuard { """
             compareGradleBuilds {
-                sourceBuild.gradleVersion "${previous.version}"
+                sourceBuild.gradleVersion "${previous.version.version}"
             }
         """ }
 
@@ -51,8 +51,8 @@ class Pre12CompareGradleBuildsCrossVersionSpec extends CrossVersionIntegrationSp
         sourceWasInferred()
 
         then:
-        sourceBuildVersion == previous.version
-        targetBuildVersion == current.version
+        sourceBuildVersion == previous.version.version
+        targetBuildVersion == current.version.version
     }
 
     def "can compare identical builds with target pre 1.2"() {
@@ -61,7 +61,7 @@ class Pre12CompareGradleBuildsCrossVersionSpec extends CrossVersionIntegrationSp
         buildFile << "apply plugin: 'java'"
         versionGuard { """
             compareGradleBuilds {
-                targetBuild.gradleVersion "${previous.version}"
+                targetBuild.gradleVersion "${previous.version.version}"
             }
         """ }
 
@@ -73,8 +73,8 @@ class Pre12CompareGradleBuildsCrossVersionSpec extends CrossVersionIntegrationSp
         targetWasInferred()
 
         then:
-        sourceBuildVersion == current.version
-        targetBuildVersion == previous.version
+        sourceBuildVersion == current.version.version
+        targetBuildVersion == previous.version.version
     }
 
     def "can compare different builds with source pre 1.2"() {
@@ -83,7 +83,7 @@ class Pre12CompareGradleBuildsCrossVersionSpec extends CrossVersionIntegrationSp
         buildFile << "apply plugin: 'java'"
         versionGuard { """
             compareGradleBuilds {
-                sourceBuild.gradleVersion "${previous.version}"
+                sourceBuild.gradleVersion "${previous.version.version}"
             }
 
             compileJava { options.debug = !options.debug }
@@ -97,8 +97,8 @@ class Pre12CompareGradleBuildsCrossVersionSpec extends CrossVersionIntegrationSp
         sourceWasInferred()
 
         then:
-        sourceBuildVersion == previous.version
-        targetBuildVersion == current.version
+        sourceBuildVersion == previous.version.version
+        targetBuildVersion == current.version.version
     }
 
     def "can compare different builds with target pre 1.2"() {
@@ -107,7 +107,7 @@ class Pre12CompareGradleBuildsCrossVersionSpec extends CrossVersionIntegrationSp
         buildFile << "apply plugin: 'java'"
         versionGuard { """
             compareGradleBuilds {
-                targetBuild.gradleVersion "${previous.version}"
+                targetBuild.gradleVersion "${previous.version.version}"
             }
 
             compileJava { options.debug = !options.debug }
@@ -121,12 +121,12 @@ class Pre12CompareGradleBuildsCrossVersionSpec extends CrossVersionIntegrationSp
         targetWasInferred()
 
         then:
-        sourceBuildVersion == current.version
-        targetBuildVersion == previous.version
+        sourceBuildVersion == current.version.version
+        targetBuildVersion == previous.version.version
     }
 
     protected versionGuard(TestFile file = buildFile, Closure string) {
-        file << "\nif (GradleVersion.current().version == '${current.version}') {\n"
+        file << "\nif (GradleVersion.current().version == '${current.version.version}') {\n"
         file << string()
         file << "\n}\n"
     }
@@ -137,7 +137,7 @@ class Pre12CompareGradleBuildsCrossVersionSpec extends CrossVersionIntegrationSp
     }
 
     protected GradleExecuter currentExecuter() {
-        current.executer().withForkingExecuter().withTasks("compareGradleBuilds")
+        current.executer(temporaryFolder).requireGradleHome(true).withTasks("compareGradleBuilds")
     }
 
     Document html(path = "build/reports/compareGradleBuilds/index.html") {
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildComparison.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildComparison.java
index af3188b..83720a4 100644
--- a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildComparison.java
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildComparison.java
@@ -16,11 +16,10 @@
 
 package org.gradle.api.plugins.buildcomparison.gradle.internal;
 
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.IOUtils;
+import org.gradle.api.Action;
 import org.gradle.api.GradleException;
 import org.gradle.api.Transformer;
-import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.IoActions;
 import org.gradle.api.internal.filestore.FileStore;
 import org.gradle.api.invocation.Gradle;
 import org.gradle.api.logging.Logger;
@@ -39,7 +38,9 @@ import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes;
 import org.gradle.util.GFileUtils;
 import org.gradle.util.GradleVersion;
 
-import java.io.*;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.Writer;
 import java.nio.charset.Charset;
 import java.util.LinkedList;
 import java.util.List;
@@ -230,30 +231,19 @@ public class GradleBuildComparison {
         return connector.connect();
     }
 
-    private void writeReport(BuildComparisonResult result, File reportDir, FileStore<String> fileStore, Map<String, String> hostAttributes) {
+    private void writeReport(final BuildComparisonResult result, final File reportDir, FileStore<String> fileStore, final Map<String, String> hostAttributes) {
         if (reportDir.exists() && reportDir.list().length > 0) {
             GFileUtils.cleanDirectory(reportDir);
         }
 
         fileStore.moveFilestore(new File(reportDir, FILES_DIR_NAME));
 
-        Charset encoding = Charset.defaultCharset();
-        OutputStream outputStream;
-        Writer writer;
-
-        try {
-            outputStream = FileUtils.openOutputStream(new File(reportDir, HTML_REPORT_FILE_NAME));
-            writer = new OutputStreamWriter(outputStream, encoding);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-
-        try {
-            createResultRenderer(encoding, reportDir, hostAttributes).render(result, writer);
-        } finally {
-            IOUtils.closeQuietly(writer);
-            IOUtils.closeQuietly(outputStream);
-        }
+        final Charset encoding = Charset.defaultCharset();
+        IoActions.writeFile(new File(reportDir, HTML_REPORT_FILE_NAME), encoding.name(), new Action<BufferedWriter>() {
+            public void execute(BufferedWriter writer) {
+                createResultRenderer(encoding, reportDir, hostAttributes).render(result, writer);
+            }
+        });
     }
 
     private BuildComparisonResultRenderer<Writer> createResultRenderer(Charset encoding, final File reportDir, final Map<String, String> hostAttributes) {
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetInferrerTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetInferrerTest.groovy
index c1b0779..19ceb76 100644
--- a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetInferrerTest.groovy
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetInferrerTest.groovy
@@ -20,11 +20,11 @@ import org.gradle.api.internal.filestore.PathNormalisingKeyFileStore
 import org.gradle.api.plugins.buildcomparison.fixtures.ProjectOutcomesBuilder
 import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.GeneratedArchiveBuildOutcome
 import org.gradle.api.plugins.buildcomparison.outcome.internal.unknown.UnknownBuildOutcome
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.tooling.model.internal.outcomes.GradleBuildOutcome
 import org.gradle.tooling.model.internal.outcomes.GradleFileBuildOutcome
 import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -32,7 +32,7 @@ import static org.gradle.tooling.internal.provider.FileOutcomeIdentifier.*
 
 class GradleBuildOutcomeSetInferrerTest extends Specification {
 
-    @Rule TemporaryFolder dir = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider dir = new TestNameTestDirectoryProvider()
 
     def inferBase = dir.createDir("infer-base")
     def store = new PathNormalisingKeyFileStore(dir.createDir("fs"))
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetTransformerTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetTransformerTest.groovy
index 0fc35f6..84529dd 100644
--- a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetTransformerTest.groovy
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetTransformerTest.groovy
@@ -16,22 +16,22 @@
 
 package org.gradle.api.plugins.buildcomparison.gradle.internal
 
+import org.gradle.api.Action
+import org.gradle.api.internal.filestore.AbstractFileStoreEntry
 import org.gradle.api.internal.filestore.FileStore
 import org.gradle.api.internal.filestore.FileStoreEntry
 import org.gradle.api.plugins.buildcomparison.fixtures.ProjectOutcomesBuilder
 import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.GeneratedArchiveBuildOutcome
 import org.gradle.api.plugins.buildcomparison.outcome.internal.unknown.UnknownBuildOutcome
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.tooling.model.internal.outcomes.GradleBuildOutcome
+import org.gradle.tooling.model.internal.outcomes.GradleFileBuildOutcome
 import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes
+import org.junit.Rule
 import spock.lang.Specification
 
 import static org.gradle.tooling.internal.provider.FileOutcomeIdentifier.*
-import org.gradle.api.internal.filestore.AbstractFileStoreEntry
-import org.gradle.util.TestFile
-import org.gradle.tooling.model.internal.outcomes.GradleFileBuildOutcome
-import org.gradle.tooling.model.internal.outcomes.GradleBuildOutcome
-import org.gradle.api.Action
-import org.junit.Rule
-import org.gradle.util.TemporaryFolder
 
 class GradleBuildOutcomeSetTransformerTest extends Specification {
 
@@ -62,12 +62,12 @@ class GradleBuildOutcomeSetTransformerTest extends Specification {
     }
 
     def transformer = new GradleBuildOutcomeSetTransformer(store, "things")
-    @Rule TemporaryFolder dir = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider dir = new TestNameTestDirectoryProvider()
 
     def "can transform"() {
         given:
         ProjectOutcomesBuilder builder = new ProjectOutcomesBuilder()
-        ProjectOutcomes projectOutput = builder.build(dir.dir) {
+        ProjectOutcomes projectOutput = builder.build(dir.testDirectory) {
             createChild("a") {
                 addFile "a1", JAR_ARTIFACT.typeIdentifier
             }
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeComparatorTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeComparatorTest.groovy
index 2328c70..b5a71bc 100644
--- a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeComparatorTest.groovy
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeComparatorTest.groovy
@@ -21,7 +21,7 @@ import org.gradle.api.internal.filestore.AbstractFileStoreEntry
 import org.gradle.api.plugins.buildcomparison.outcome.internal.DefaultBuildOutcomeAssociation
 import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.entry.ArchiveEntry
 import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.entry.ArchiveEntryComparison
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -29,7 +29,7 @@ import static org.gradle.api.plugins.buildcomparison.compare.internal.Comparison
 
 class GeneratedArchiveBuildOutcomeComparatorTest extends Specification {
 
-    @Rule TemporaryFolder dir = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider dir = new TestNameTestDirectoryProvider()
 
     def transformer = Mock(Transformer)
     def comparator = new GeneratedArchiveBuildOutcomeComparator(transformer)
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/FileToArchiveEntrySetTransformerTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/FileToArchiveEntrySetTransformerTest.groovy
index a3cf521..e34830e 100644
--- a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/FileToArchiveEntrySetTransformerTest.groovy
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/FileToArchiveEntrySetTransformerTest.groovy
@@ -16,14 +16,14 @@
 
 package org.gradle.api.plugins.buildcomparison.outcome.internal.archive.entry
 
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 class FileToArchiveEntrySetTransformerTest extends Specification {
 
-    @Rule TemporaryFolder dir = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider dir = new TestNameTestDirectoryProvider()
     Set<ArchiveEntry> entrySet
 
     def transformer = new FileToArchiveEntrySetTransformer(new ZipEntryToArchiveEntryTransformer())
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/GradleBuildComparisonResultHtmlRendererTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/GradleBuildComparisonResultHtmlRendererTest.groovy
index 57e2fca..e0b2180 100644
--- a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/GradleBuildComparisonResultHtmlRendererTest.groovy
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/GradleBuildComparisonResultHtmlRendererTest.groovy
@@ -31,7 +31,7 @@ import org.gradle.api.plugins.buildcomparison.outcome.string.StringBuildOutcomeH
 import org.gradle.api.plugins.buildcomparison.render.internal.BuildComparisonResultRenderer
 import org.gradle.api.plugins.buildcomparison.render.internal.DefaultBuildOutcomeComparisonResultRendererFactory
 import org.gradle.api.plugins.buildcomparison.render.internal.DefaultBuildOutcomeRendererFactory
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.jsoup.Jsoup
 import org.jsoup.nodes.Document
 import org.junit.Rule
@@ -41,7 +41,7 @@ import java.nio.charset.Charset
 
 class GradleBuildComparisonResultHtmlRendererTest extends Specification {
 
-    @Rule TemporaryFolder dir = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider dir = new TestNameTestDirectoryProvider()
 
     def comparisonRenderers = new DefaultBuildOutcomeComparisonResultRendererFactory(HtmlRenderContext)
     def outcomeRenderers = new DefaultBuildOutcomeRendererFactory(HtmlRenderContext)
@@ -56,7 +56,7 @@ class GradleBuildComparisonResultHtmlRendererTest extends Specification {
 
     def sourceBuildDir = dir.createDir("source")
     def targetBuildDir = dir.createDir("target")
-    def resolver = TestFiles.resolver(dir.dir)
+    def resolver = TestFiles.resolver(dir.testDirectory)
     def sourceBuildSpec = new DefaultGradleBuildInvocationSpec(resolver, sourceBuildDir)
     def targetBuildSpec = new DefaultGradleBuildInvocationSpec(resolver, targetBuildDir)
     def sourceBuildExecuter = new ComparableGradleBuildExecuter(sourceBuildSpec)
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CheckstylePluginIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CheckstylePluginIntegrationTest.groovy
index 3d46951..2d8d517 100644
--- a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CheckstylePluginIntegrationTest.groovy
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CheckstylePluginIntegrationTest.groovy
@@ -17,6 +17,7 @@ package org.gradle.api.plugins.quality
 
 import org.gradle.integtests.fixtures.WellBehavedPluginTest
 import org.hamcrest.Matcher
+
 import static org.gradle.util.Matchers.containsLine
 import static org.hamcrest.Matchers.containsString
 import static org.hamcrest.Matchers.startsWith
@@ -50,6 +51,23 @@ class CheckstylePluginIntegrationTest extends WellBehavedPluginTest {
         fails("check")
         failure.assertHasDescription("Execution failed for task ':checkstyleMain'")
         failure.assertThatCause(startsWith("Checkstyle rule violations were found. See the report at:"))
+        failure.error.contains("Name 'class1' must match pattern")
+        file("build/reports/checkstyle/main.xml").assertContents(containsClass("org.gradle.class1"))
+        file("build/reports/checkstyle/main.xml").assertContents(containsClass("org.gradle.class2"))
+    }
+
+    def "can suppress console output"() {
+        given:
+        badCode()
+
+        when:
+        buildFile << "checkstyle { showViolations = false }"
+
+        then:
+        fails("check")
+        failure.assertHasDescription("Execution failed for task ':checkstyleMain'")
+        failure.assertThatCause(startsWith("Checkstyle rule violations were found. See the report at:"))
+        !failure.error.contains("Name 'class1' must match pattern")
         file("build/reports/checkstyle/main.xml").assertContents(containsClass("org.gradle.class1"))
         file("build/reports/checkstyle/main.xml").assertContents(containsClass("org.gradle.class2"))
     }
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeQualityPluginIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeQualityPluginIntegrationTest.groovy
index dcda76a..3ef3675 100644
--- a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeQualityPluginIntegrationTest.groovy
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeQualityPluginIntegrationTest.groovy
@@ -15,13 +15,14 @@
  */
 package org.gradle.api.plugins.quality
 
-import org.gradle.integtests.fixtures.ExecutionFailure
-import org.gradle.util.TestFile
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.executer.ExecutionFailure
+import org.gradle.test.fixtures.file.TestFile
 import org.hamcrest.Matcher
 import org.junit.Test
-import static org.gradle.util.Matchers.*
+
+import static org.gradle.util.Matchers.containsLine
 import static org.hamcrest.Matchers.*
-import org.gradle.integtests.fixtures.AbstractIntegrationTest
 
 class CodeQualityPluginIntegrationTest extends AbstractIntegrationTest {
     {
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/internal/FindBugsSpecBuilderTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/internal/FindBugsSpecBuilderTest.groovy
index 99e2544..dc84dd5 100644
--- a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/internal/FindBugsSpecBuilderTest.groovy
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/internal/FindBugsSpecBuilderTest.groovy
@@ -19,16 +19,14 @@ package org.gradle.api.plugins.quality.internal
 import org.gradle.api.InvalidUserDataException
 import org.gradle.api.NamedDomainObjectSet
 import org.gradle.api.file.FileCollection
-import org.gradle.api.reporting.SingleFileReport
 import org.gradle.api.plugins.quality.internal.findbugs.FindBugsSpecBuilder
-import org.gradle.util.TemporaryFolder
-
+import org.gradle.api.reporting.SingleFileReport
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
-
 import spock.lang.Specification
 
 class FindBugsSpecBuilderTest extends Specification {
-    @Rule TemporaryFolder tempFolder = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider tempFolder = new TestNameTestDirectoryProvider()
 
     FileCollection classes = Mock()
     FindBugsSpecBuilder builder = new FindBugsSpecBuilder(classes)
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.groovy
index af18000..d385a93 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.groovy
@@ -148,6 +148,11 @@ class Checkstyle extends SourceTask implements VerificationTask, Reporting<Check
      */
     boolean ignoreFailures
 
+    /**
+     * Whether or not rule violations are to be displayed on the console.
+     */
+    boolean showViolations = true
+
     @TaskAction
     public void run() {
         def propertyName = "org.gradle.checkstyle.violations"
@@ -157,7 +162,9 @@ class Checkstyle extends SourceTask implements VerificationTask, Reporting<Check
             ant.checkstyle(config: getConfigFile(), failOnViolation: false, failureProperty: propertyName) {
                 getSource().addToAntBuilder(ant, 'fileset', FileCollection.AntType.FileSet)
                 getClasspath().addToAntBuilder(ant, 'classpath')
-                formatter(type: 'plain', useFile: false)
+                if (showViolations) {
+                    formatter(type: 'plain', useFile: false)
+                }
                 if (reports.xml.enabled) {
                     formatter(type: 'xml', toFile: reports.xml.destination)
                 }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstyleExtension.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstyleExtension.groovy
index b1c7b5f..d3be0b7 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstyleExtension.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstyleExtension.groovy
@@ -26,4 +26,11 @@ class CheckstyleExtension extends CodeQualityExtension {
      * file.
      */
     Map<String, Object> configProperties = [:]
+
+    /**
+     * Whether or not rule violations are to be displayed on the console. Defaults to <tt>true</tt>.
+     *
+     * Example: showViolations = false
+     */
+    boolean showViolations = true
 }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstylePlugin.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstylePlugin.groovy
index 3957427..7796ef0 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstylePlugin.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstylePlugin.groovy
@@ -36,7 +36,7 @@ class CheckstylePlugin extends AbstractCodeQualityPlugin<Checkstyle> {
         extension = project.extensions.create("checkstyle", CheckstyleExtension)
 
         extension.with {
-            toolVersion = "5.5"
+            toolVersion = "5.6"
             configFile = project.file("config/checkstyle/checkstyle.xml")
         }
 
@@ -58,6 +58,7 @@ class CheckstylePlugin extends AbstractCodeQualityPlugin<Checkstyle> {
             configFile = { extension.configFile }
             configProperties = { extension.configProperties }
             ignoreFailures = { extension.ignoreFailures }
+            showViolations = { extension.showViolations }
         }
 
         task.reports.xml.conventionMapping.with {
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcPlugin.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcPlugin.groovy
index 77acf6f..fc3cc73 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcPlugin.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcPlugin.groovy
@@ -41,7 +41,7 @@ class CodeNarcPlugin extends AbstractCodeQualityPlugin<CodeNarc> {
     protected CodeQualityExtension createExtension() {
         extension = project.extensions.create("codenarc", CodeNarcExtension)
         extension.with {
-            toolVersion = "0.16.1"
+            toolVersion = "0.18"
             configFile = project.rootProject.file("config/codenarc/codenarc.xml")
             reportFormat = "html"
         }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.groovy
index 57101d2..00da2cb 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.groovy
@@ -57,7 +57,7 @@ class Pmd extends SourceTask implements VerificationTask, Reporting<PmdReports>
     FileCollection ruleSetFiles
 
     @Nested
-    private final PmdReportsImpl reports = services.get(Instantiator).newInstance(PmdReportsImpl, this)
+    private final PmdReportsImpl reports
 
     private final IsolatedAntBuilder antBuilder
 
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CheckstylePluginTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CheckstylePluginTest.groovy
index 3e19918..afadcfd 100644
--- a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CheckstylePluginTest.groovy
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CheckstylePluginTest.groovy
@@ -19,11 +19,13 @@ import org.gradle.api.Project
 import org.gradle.api.plugins.ReportingBasePlugin
 import org.gradle.api.tasks.SourceSet
 import org.gradle.util.HelperUtil
+import org.gradle.api.plugins.JavaBasePlugin
+
 import spock.lang.Specification
+
 import static org.gradle.util.Matchers.dependsOn
 import static org.hamcrest.Matchers.*
 import static spock.util.matcher.HamcrestSupport.that
-import org.gradle.api.plugins.JavaBasePlugin
 
 class CheckstylePluginTest extends Specification {
     Project project = HelperUtil.createRootProject()
@@ -80,7 +82,8 @@ class CheckstylePluginTest extends Specification {
             assert configFile == project.file("config/checkstyle/checkstyle.xml")
             assert configProperties == [:]
             assert resultFile == project.file("build/reports/checkstyle/${sourceSet.name}.xml")
-            assert ignoreFailures == false
+            assert !ignoreFailures
+            assert showViolations
         }
     }
     
@@ -94,7 +97,7 @@ class CheckstylePluginTest extends Specification {
         task.configFile == project.file("config/checkstyle/checkstyle.xml")
         task.configProperties == [:]
         task.resultFile == project.file("build/reports/checkstyle/custom.xml")
-        task.ignoreFailures == false
+        !task.ignoreFailures
     }
 
     def "adds checkstyle tasks to check lifecycle task"() {
@@ -123,6 +126,7 @@ class CheckstylePluginTest extends Specification {
             configProperties = [foo: "foo"]
             reportsDir = project.file("checkstyle-reports")
             ignoreFailures = true
+            displayViolations = true
         }
         
         expect:
@@ -143,7 +147,8 @@ class CheckstylePluginTest extends Specification {
             assert configFile == project.file("checkstyle-config")
             assert configProperties == [foo: "foo"]
             assert resultFile == project.file("checkstyle-reports/${sourceSet.name}.xml")
-            assert ignoreFailures == true
+            assert ignoreFailures
+            assert showViolations
         }
     }
     
@@ -163,7 +168,7 @@ class CheckstylePluginTest extends Specification {
         task.configFile == project.file("checkstyle-config")
         task.configProperties == [foo: "foo"]
         task.resultFile == project.file("checkstyle-reports/custom.xml")
-        task.ignoreFailures == true
+        task.ignoreFailures
     }
     
 }
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CheckstyleTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CheckstyleTest.groovy
new file mode 100644
index 0000000..6b72fd4
--- /dev/null
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CheckstyleTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.plugins.quality
+
+import org.gradle.api.reporting.Report
+import org.gradle.testfixtures.ProjectBuilder
+import spock.lang.Specification
+
+class CheckstyleTest extends Specification {
+    def "default configuration"() {
+        def project = ProjectBuilder.builder().build()
+        def checkstyle = project.tasks.add("checkstyle", Checkstyle)
+
+        expect:
+        with(checkstyle) {
+            checkstyleClasspath == null
+            classpath == null
+            configFile == null
+            configProperties == [:]
+            !reports.xml.enabled
+            reports.xml.destination == null
+            reports.xml.outputType == Report.OutputType.FILE
+            !ignoreFailures
+            showViolations
+        }
+    }
+}
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/FindBugsPluginTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/FindBugsPluginTest.groovy
index b983769..e7640cd 100644
--- a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/FindBugsPluginTest.groovy
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/FindBugsPluginTest.groovy
@@ -79,19 +79,19 @@ class FindBugsPluginTest extends Specification {
     private void configuresFindBugsTask(String taskName, SourceSet sourceSet) {
         def task = project.tasks.findByName(taskName)
         assert task instanceof FindBugs
-        task.with {
-            assert description == "Run FindBugs analysis for ${sourceSet.name} classes"
-            assert source as List == sourceSet.allJava as List
-            assert findbugsClasspath == project.configurations.findbugs
-            assert classes.empty // no classes to analyze
-            assert reports.xml.destination == project.file("build/reports/findbugs/${sourceSet.name}.xml")
-            assert !ignoreFailures
-            assert effort == null
-            assert reportLevel == null
-            assert visitors == null
-            assert omitVisitors == null
-            assert excludeFilter == null
-            assert includeFilter == null
+        with(task) {
+            description == "Run FindBugs analysis for ${sourceSet.name} classes"
+            source as List == sourceSet.allJava as List
+            findbugsClasspath == project.configurations.findbugs
+            classes.empty // no classes to analyze
+            reports.xml.destination == project.file("build/reports/findbugs/${sourceSet.name}.xml")
+            !ignoreFailures
+            effort == null
+            reportLevel == null
+            visitors == null
+            omitVisitors == null
+            excludeFilter == null
+            includeFilter == null
         }
     }
 
@@ -99,20 +99,22 @@ class FindBugsPluginTest extends Specification {
         def task = project.tasks.add("findbugsCustom", FindBugs)
 
         expect:
-        task.description == null
-        task.source.empty
-        task.classes == null
-        task.classpath == null
-        task.findbugsClasspath == project.configurations.findbugs
-        task.pluginClasspath == project.configurations.findbugsPlugins
-        task.reports.xml.destination == project.file("build/reports/findbugs/custom.xml")
-        !task.ignoreFailures
-        task.effort == null
-        task.reportLevel == null
-        task.visitors == null
-        task.omitVisitors == null
-        task.excludeFilter == null
-        task.includeFilter == null
+        with(task) {
+            description == null
+            source.empty
+            classes == null
+            classpath == null
+            findbugsClasspath == project.configurations.findbugs
+            pluginClasspath == project.configurations.findbugsPlugins
+            reports.xml.destination == project.file("build/reports/findbugs/custom.xml")
+            !ignoreFailures
+            effort == null
+            reportLevel == null
+            visitors == null
+            omitVisitors == null
+            excludeFilter == null
+            includeFilter == null
+        }
     }
 
     def "adds FindBugs tasks to check lifecycle task"() {
@@ -158,18 +160,18 @@ class FindBugsPluginTest extends Specification {
     private void hasCustomizedSettings(String taskName, SourceSet sourceSet) {
         def task = project.tasks.findByName(taskName)
         assert task instanceof FindBugs
-        task.with {
-            assert description == "Run FindBugs analysis for ${sourceSet.name} classes"
-            assert source as List == sourceSet.allJava as List
-            assert findbugsClasspath == project.configurations.findbugs
-            assert reports.xml.destination == project.file("findbugs-reports/${sourceSet.name}.xml")
-            assert ignoreFailures
-            assert effort == 'min'
-            assert reportLevel == 'high'
-            assert visitors == ['org.gradle.Class']
-            assert omitVisitors == ['org.gradle.Interface']
-            assert includeFilter == new File("include.txt")
-            assert excludeFilter == new File("exclude.txt")
+        with(task) {
+            description == "Run FindBugs analysis for ${sourceSet.name} classes"
+            source as List == sourceSet.allJava as List
+            findbugsClasspath == project.configurations.findbugs
+            reports.xml.destination == project.file("findbugs-reports/${sourceSet.name}.xml")
+            ignoreFailures
+            effort == 'min'
+            reportLevel == 'high'
+            visitors == ['org.gradle.Class']
+            omitVisitors == ['org.gradle.Interface']
+            includeFilter == new File("include.txt")
+            excludeFilter == new File("exclude.txt")
         }
     }
     
@@ -187,20 +189,22 @@ class FindBugsPluginTest extends Specification {
         }
 
         expect:
-        task.description == null
-        task.source.empty
-        task.classes == null
-        task.classpath == null
-        task.findbugsClasspath == project.configurations.findbugs
-        task.pluginClasspath == project.configurations.findbugsPlugins
-        task.reports.xml.destination == project.file("findbugs-reports/custom.xml")
-        task.ignoreFailures
-        task.effort == 'min'
-        task.reportLevel == 'high'
-        task.visitors == ['org.gradle.Class']
-        task.omitVisitors == ['org.gradle.Interface']
-        task.includeFilter == new File("include.txt")
-        task.excludeFilter == new File("exclude.txt")
+        with(task) {
+            description == null
+            source.empty
+            classes == null
+            classpath == null
+            findbugsClasspath == project.configurations.findbugs
+            pluginClasspath == project.configurations.findbugsPlugins
+            reports.xml.destination == project.file("findbugs-reports/custom.xml")
+            ignoreFailures
+            effort == 'min'
+            reportLevel == 'high'
+            visitors == ['org.gradle.Class']
+            omitVisitors == ['org.gradle.Interface']
+            includeFilter == new File("include.txt")
+            excludeFilter == new File("exclude.txt")
+        }
     }
 
     def "can configure reporting"() {
diff --git a/subprojects/core-impl/core-impl.gradle b/subprojects/core-impl/core-impl.gradle
index af31644..495307e 100644
--- a/subprojects/core-impl/core-impl.gradle
+++ b/subprojects/core-impl/core-impl.gradle
@@ -20,13 +20,14 @@ dependencies {
     compile libraries.nekohtml
     runtime libraries.xbean //maven3 classes dependency
 
-    testCompile libraries.junit
-
     compile fileTree("$buildDir/libs/jarjar") {
         builtBy 'jarJarMaven3'
     }
 
     mvn3Input libraries.maven3
+
+    //this dependency is necessary to run IvySFtpResolverIntegrationTest on ibm jdk
+    integTestRuntime "org.bouncycastle:bcprov-jdk15:1.46 at jar"
 }
 
 task jarJarMaven3(type: JarJar) {
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy
new file mode 100644
index 0000000..c68c402
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy
@@ -0,0 +1,659 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve
+
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.executer.ExecutionFailure
+import org.gradle.test.fixtures.file.TestFile
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import spock.lang.Issue
+
+import static org.hamcrest.Matchers.containsString
+import static org.hamcrest.Matchers.startsWith
+
+class ArtifactDependenciesIntegrationTest extends AbstractIntegrationTest {
+    @Rule
+    public final TestResources testResources = new TestResources()
+
+    @Before
+    public void setup() {
+        executer.requireOwnGradleUserHomeDir()
+    }
+
+    @Test
+    public void canHaveConfigurationHierarchy() {
+        File buildFile = testFile("projectWithConfigurationHierarchy.gradle");
+        usingBuildFile(buildFile).run();
+    }
+
+    @Test
+    public void dependencyReportWithConflicts() {
+        File buildFile = testFile("projectWithConflicts.gradle");
+        usingBuildFile(buildFile).run();
+        usingBuildFile(buildFile).withDependencyList().run();
+    }
+
+    @Test
+    public void canNestModules() throws IOException {
+        File buildFile = testFile("projectWithNestedModules.gradle");
+        usingBuildFile(buildFile).run();
+    }
+
+    @Test
+    public void canHaveCycleInDependencyGraph() throws IOException {
+        File buildFile = testFile("projectWithCyclesInDependencyGraph.gradle");
+        usingBuildFile(buildFile).run();
+    }
+
+    @Test
+    public void canUseDynamicVersions() throws IOException {
+        File buildFile = testFile("projectWithDynamicVersions.gradle");
+        usingBuildFile(buildFile).run();
+    }
+
+    @Test
+    public void resolutionFailsWhenProjectHasNoRepositoriesEvenWhenArtifactIsCachedLocally() {
+        testFile('settings.gradle') << 'include "a", "b"'
+        testFile('build.gradle') << """
+subprojects {
+    configurations {
+        compile
+    }
+    task listDeps << { configurations.compile.each { } }
+}
+project(':a') {
+    repositories {
+        maven { url '${repo.uri}' }
+    }
+    dependencies {
+        compile 'org.gradle.test:external1:1.0'
+    }
+}
+project(':b') {
+    dependencies {
+        compile 'org.gradle.test:external1:1.0'
+    }
+}
+"""
+        repo.module('org.gradle.test', 'external1', '1.0').publish()
+
+        inTestDirectory().withTasks('a:listDeps').run()
+        def result = inTestDirectory().withTasks('b:listDeps').runWithFailure()
+        result.assertThatCause(containsString('Could not find org.gradle.test:external1:1.0.'))
+    }
+
+    @Test
+    public void resolutionFailsForMissingArtifact() {
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    compile; missingExt; missingClassifier
+}
+dependencies {
+    compile "org.gradle.test:lib:1.0"
+    missingExt "org.gradle.test:lib:1.0 at zip"
+    missingClassifier "org.gradle.test:lib:1.0:classifier1"
+}
+task listJar << { configurations.compile.each { } }
+task listMissingExt << { configurations.missingExt.each { } }
+task listMissingClassifier << { configurations.missingClassifier.each { } }
+"""
+        repo.module('org.gradle.test', 'lib', '1.0').publish()
+
+        inTestDirectory().withTasks('listJar').run()
+
+        def result = inTestDirectory().withTasks('listMissingExt').runWithFailure()
+        result.assertThatCause(containsString("Artifact 'org.gradle.test:lib:1.0 at zip' not found"))
+
+        result = inTestDirectory().withTasks('listMissingClassifier').runWithFailure()
+        result.assertThatCause(containsString("Artifact 'org.gradle.test:lib:1.0:classifier1 at jar' not found"))
+    }
+
+    @Test
+    @Issue("GRADLE-1342")
+    public void resolutionDoesNotUseCachedArtifactFromDifferentRepository() {
+        def repo1 = maven('repo1')
+        repo1.module('org.gradle.test', 'external1', '1.0').publish()
+        def repo2 = maven('repo2')
+
+        testFile('settings.gradle') << 'include "a", "b"'
+        testFile('build.gradle') << """
+subprojects {
+    configurations {
+        compile
+    }
+    task listDeps << { configurations.compile.each { } }
+}
+project(':a') {
+    repositories {
+        maven { url '${repo1.uri}' }
+    }
+    dependencies {
+        compile 'org.gradle.test:external1:1.0'
+    }
+}
+project(':b') {
+    repositories {
+        maven { url '${repo2.uri}' }
+    }
+    dependencies {
+        compile 'org.gradle.test:external1:1.0'
+    }
+}
+"""
+
+        inTestDirectory().withTasks('a:listDeps').run()
+        def result = inTestDirectory().withTasks('b:listDeps').runWithFailure()
+        result.assertThatCause(containsString('Could not find org.gradle.test:external1:1.0.'))
+    }
+
+    @Test
+    public void exposesMetaDataAboutResolvedArtifactsInAFixedOrder() {
+        def module = repo.module('org.gradle.test', 'lib', '1.0')
+        module.artifact(type: 'zip')
+        module.artifact(classifier: 'classifier')
+        module.publish()
+        repo.module('org.gradle.test', 'dist', '1.0').hasType('zip').publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    compile
+}
+dependencies {
+    compile "org.gradle.test:lib:1.0"
+    compile "org.gradle.test:lib:1.0:classifier"
+    compile "org.gradle.test:lib:1.0 at zip"
+    compile "org.gradle.test:dist:1.0"
+}
+task test << {
+    assert configurations.compile.files.collect { it.name } == ['lib-1.0.jar', 'lib-1.0.zip', 'lib-1.0-classifier.jar', 'dist-1.0.zip']
+    def artifacts = configurations.compile.resolvedConfiguration.resolvedArtifacts as List
+    assert artifacts.size() == 4
+    assert artifacts[0].name == 'lib'
+    assert artifacts[0].type == 'jar'
+    assert artifacts[0].extension == 'jar'
+    assert artifacts[0].classifier == null
+    assert artifacts[1].name == 'lib'
+    assert artifacts[1].type == 'jar'
+    assert artifacts[1].extension == 'jar'
+    assert artifacts[1].classifier == 'classifier'
+    assert artifacts[2].name == 'lib'
+    assert artifacts[2].type == 'zip'
+    assert artifacts[2].extension == 'zip'
+    assert artifacts[2].classifier == null
+    assert artifacts[3].name == 'dist'
+    assert artifacts[3].type == 'zip'
+    assert artifacts[3].extension == 'zip'
+    assert artifacts[3].classifier == null
+}
+"""
+
+        executer.withDeprecationChecksDisabled()
+        def result = inTestDirectory().withTasks('test').run()
+        assert result.output.contains('Relying on packaging to define the extension of the main artifact has been deprecated')
+    }
+
+    @Test
+    @Issue("GRADLE-1567")
+    public void resolutionDifferentiatesBetweenArtifactsThatDifferOnlyInClassifier() {
+        def module = repo.module('org.gradle.test', 'external1', '1.0')
+        module.artifact(classifier: 'classifier1')
+        module.artifact(classifier: 'classifier2')
+        module.publish()
+
+        testFile('settings.gradle') << 'include "a", "b", "c"'
+        testFile('build.gradle') << """
+subprojects {
+    repositories {
+        maven { url '${repo.uri}' }
+    }
+    configurations {
+        compile
+    }
+}
+project(':a') {
+    dependencies {
+        compile 'org.gradle.test:external1:1.0:classifier1'
+    }
+    task test(dependsOn: configurations.compile) << {
+        assert configurations.compile.collect { it.name } == ['external1-1.0-classifier1.jar']
+        assert configurations.compile.resolvedConfiguration.resolvedArtifacts.collect { "\${it.name}-\${it.classifier}" } == ['external1-classifier1']
+    }
+}
+project(':b') {
+    dependencies {
+        compile 'org.gradle.test:external1:1.0:classifier2'
+    }
+    task test(dependsOn: configurations.compile) << {
+        assert configurations.compile.collect { it.name } == ['external1-1.0-classifier2.jar']
+        assert configurations.compile.resolvedConfiguration.resolvedArtifacts.collect { "\${it.name}-\${it.classifier}" } == ['external1-classifier2']
+    }
+}
+"""
+
+        inTestDirectory().withTasks('a:test').run()
+        inTestDirectory().withTasks('b:test').run()
+    }
+
+    @Test
+    @Issue("GRADLE-739")
+    public void singleConfigurationCanContainMultipleArtifactsThatOnlyDifferByClassifier() {
+        def module = repo.module('org.gradle.test', 'external1', '1.0')
+        module.artifact(classifier: 'baseClassifier')
+        module.artifact(classifier: 'extendedClassifier')
+        module.publish()
+        repo.module('org.gradle.test', 'other', '1.0').publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    base
+    extendedWithClassifier.extendsFrom base
+    extendedWithOther.extendsFrom base
+    justDefault
+    justClassifier
+    rawBase
+    rawExtended.extendsFrom rawBase
+    cBase
+    cExtended.extendsFrom cBase
+}
+dependencies {
+    base 'org.gradle.test:external1:1.0'
+    base 'org.gradle.test:external1:1.0:baseClassifier'
+    extendedWithClassifier 'org.gradle.test:external1:1.0:extendedClassifier'
+    extendedWithOther 'org.gradle.test:other:1.0'
+    justDefault 'org.gradle.test:external1:1.0'
+    justClassifier 'org.gradle.test:external1:1.0:baseClassifier'
+    justClassifier 'org.gradle.test:external1:1.0:extendedClassifier'
+    rawBase 'org.gradle.test:external1:1.0'
+    rawExtended 'org.gradle.test:external1:1.0:extendedClassifier'
+}
+
+def checkDeps(config, expectedDependencies) {
+    assert config.collect({ it.name }) as Set == expectedDependencies as Set
+}
+
+task test << {
+    checkDeps configurations.base, ['external1-1.0.jar', 'external1-1.0-baseClassifier.jar']
+    checkDeps configurations.extendedWithOther, ['external1-1.0.jar', 'external1-1.0-baseClassifier.jar', 'other-1.0.jar']
+    checkDeps configurations.extendedWithClassifier, ['external1-1.0.jar', 'external1-1.0-baseClassifier.jar', 'external1-1.0-extendedClassifier.jar']
+    checkDeps configurations.justDefault, ['external1-1.0.jar']
+    checkDeps configurations.justClassifier, ['external1-1.0-baseClassifier.jar', 'external1-1.0-extendedClassifier.jar']
+    checkDeps configurations.rawBase, ['external1-1.0.jar']
+    checkDeps configurations.rawExtended, ['external1-1.0.jar', 'external1-1.0-extendedClassifier.jar']
+}
+"""
+        inTestDirectory().withTasks('test').run()
+    }
+
+    @Test
+    @Issue("GRADLE-739")
+    public void canUseClassifiersCombinedWithArtifactWithNonStandardPackaging() {
+        def module = repo.module('org.gradle.test', 'external1', '1.0')
+        module.artifact(type: 'txt')
+        module.artifact(classifier: 'baseClassifier', type: 'jar')
+        module.artifact(classifier: 'extendedClassifier', type: 'jar')
+        module.hasType('zip')
+        module.publish()
+        repo.module('org.gradle.test', 'other', '1.0').publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    base
+    extended.extendsFrom base
+    extendedWithClassifier.extendsFrom base
+    extendedWithType.extendsFrom base
+}
+dependencies {
+    base 'org.gradle.test:external1:1.0'
+    base 'org.gradle.test:external1:1.0:baseClassifier'
+    extended 'org.gradle.test:other:1.0'
+    extendedWithClassifier 'org.gradle.test:external1:1.0:extendedClassifier'
+    extendedWithType 'org.gradle.test:external1:1.0 at txt'
+}
+
+def checkDeps(config, expectedDependencies) {
+    assert config.collect({ it.name }) as Set == expectedDependencies as Set
+}
+
+task test << {
+    checkDeps configurations.base, ['external1-1.0.zip', 'external1-1.0-baseClassifier.jar']
+    checkDeps configurations.extended, ['external1-1.0.zip', 'external1-1.0-baseClassifier.jar', 'other-1.0.jar']
+    checkDeps configurations.extendedWithClassifier, ['external1-1.0.zip', 'external1-1.0-baseClassifier.jar', 'external1-1.0-extendedClassifier.jar']
+    checkDeps configurations.extendedWithType, ['external1-1.0.zip', 'external1-1.0-baseClassifier.jar', 'external1-1.0.txt']
+}
+"""
+        executer.withDeprecationChecksDisabled()
+        def result = inTestDirectory().withTasks('test').run()
+        assert result.output.contains('Relying on packaging to define the extension of the main artifact has been deprecated')
+    }
+
+    @Test
+    @Issue("GRADLE-739")
+    public void configurationCanContainMultipleArtifactsThatOnlyDifferByType() {
+        def module = repo.module('org.gradle.test', 'external1', '1.0')
+        module.artifact(type: 'zip')
+        module.artifact(classifier: 'classifier')
+        module.artifact(classifier: 'classifier', type: 'bin')
+        module.publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    base
+    extended.extendsFrom base
+    extended2.extendsFrom base
+}
+dependencies {
+    base 'org.gradle.test:external1:1.0'
+    base 'org.gradle.test:external1:1.0 at zip'
+    extended 'org.gradle.test:external1:1.0:classifier'
+    extended2 'org.gradle.test:external1:1.0:classifier at bin'
+}
+
+def checkDeps(config, expectedDependencies) {
+    assert config.collect({ it.name }) as Set == expectedDependencies as Set
+}
+
+task test << {
+    checkDeps configurations.base, ['external1-1.0.jar', 'external1-1.0.zip']
+    checkDeps configurations.extended, ['external1-1.0.jar', 'external1-1.0.zip', 'external1-1.0-classifier.jar']
+    checkDeps configurations.extended2, ['external1-1.0.jar', 'external1-1.0.zip', 'external1-1.0-classifier.bin']
+}
+"""
+        inTestDirectory().withTasks('test').run()
+    }
+
+    @Test
+    public void "dependencies that are excluded by a dependency are not retrieved"() {
+        repo.module('org.gradle.test', 'one', '1.0').publish()
+        repo.module('org.gradle.test', 'two', '1.0').publish()
+        def module = repo.module('org.gradle.test', 'external1', '1.0')
+        module.dependsOn('org.gradle.test', 'one', '1.0')
+        module.artifact(classifier: 'classifier')
+        module.publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    reference
+    excluded
+    extendedExcluded.extendsFrom excluded
+    excludedWithClassifier
+}
+dependencies {
+    reference 'org.gradle.test:external1:1.0'
+    excluded 'org.gradle.test:external1:1.0', { exclude module: 'one' }
+    extendedExcluded 'org.gradle.test:two:1.0'
+    excludedWithClassifier 'org.gradle.test:external1:1.0', { exclude module: 'one' }
+    excludedWithClassifier 'org.gradle.test:external1:1.0:classifier', { exclude module: 'one' }
+}
+
+def checkDeps(config, expectedDependencies) {
+    assert config*.name as Set == expectedDependencies as Set
+}
+
+task test << {
+    checkDeps configurations.reference, ['external1-1.0.jar', 'one-1.0.jar']
+    checkDeps configurations.excluded, ['external1-1.0.jar']
+    checkDeps configurations.extendedExcluded, ['external1-1.0.jar', 'two-1.0.jar']
+    checkDeps configurations.excludedWithClassifier, ['external1-1.0.jar', 'external1-1.0-classifier.jar']
+}
+"""
+        inTestDirectory().withTasks('test').run()
+    }
+
+    @Test
+    public void "dependencies that are globally excluded are not retrieved"() {
+        repo.module('org.gradle.test', 'direct', '1.0').publish()
+        repo.module('org.gradle.test', 'transitive', '1.0').publish()
+        def module = repo.module('org.gradle.test', 'external', '1.0')
+        module.dependsOn('org.gradle.test', 'transitive', '1.0')
+        module.publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    excluded {
+        exclude module: 'direct'
+        exclude module: 'transitive'
+    }
+    extendedExcluded.extendsFrom excluded
+}
+dependencies {
+    excluded 'org.gradle.test:external:1.0'
+    excluded 'org.gradle.test:direct:1.0'
+}
+
+def checkDeps(config, expectedDependencies) {
+    assert config*.name as Set == expectedDependencies as Set
+}
+
+task test << {
+    checkDeps configurations.excluded, ['external-1.0.jar']
+    checkDeps configurations.extendedExcluded, ['external-1.0.jar']
+}
+"""
+        inTestDirectory().withTasks('test').run()
+    }
+
+    @Test
+    public void "does not attempt to resolve an excluded dependency"() {
+        def module = repo.module('org.gradle.test', 'external', '1.0')
+        module.dependsOn('org.gradle.test', 'unknown1', '1.0')
+        module.dependsOn('org.gradle.test', 'unknown2', '1.0')
+        module.publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    excluded {
+        exclude module: 'unknown2'
+    }
+}
+dependencies {
+    excluded 'org.gradle.test:external:1.0', { exclude module: 'unknown1' }
+    excluded 'org.gradle.test:unknown2:1.0'
+}
+
+def checkDeps(config, expectedDependencies) {
+    assert config*.name as Set == expectedDependencies as Set
+}
+
+task test << {
+    checkDeps configurations.excluded, ['external-1.0.jar']
+}
+"""
+        inTestDirectory().withTasks('test').run()
+    }
+
+    @Test
+    public void nonTransitiveDependenciesAreNotRetrieved() {
+        repo.module('org.gradle.test', 'one', '1.0').publish()
+        repo.module('org.gradle.test', 'two', '1.0').publish()
+        def module = repo.module('org.gradle.test', 'external1', '1.0')
+        module.dependsOn('org.gradle.test', 'one', '1.0')
+        module.artifact(classifier: 'classifier')
+        module.publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    transitive
+    nonTransitive
+    extendedNonTransitive.extendsFrom nonTransitive
+    extendedBoth.extendsFrom transitive, nonTransitive
+    mergedNonTransitive
+}
+dependencies {
+    transitive 'org.gradle.test:external1:1.0'
+    nonTransitive 'org.gradle.test:external1:1.0', { transitive = false }
+    extendedNonTransitive 'org.gradle.test:two:1.0'
+    mergedNonTransitive 'org.gradle.test:external1:1.0', {transitive = false }
+    mergedNonTransitive 'org.gradle.test:external1:1.0:classifier', { transitive = false }
+}
+
+def checkDeps(config, expectedDependencies) {
+    assert config.collect({ it.name }) as Set == expectedDependencies as Set
+}
+
+task test << {
+    checkDeps configurations.transitive, ['external1-1.0.jar', 'one-1.0.jar']
+    checkDeps configurations.nonTransitive, ['external1-1.0.jar']
+    checkDeps configurations.extendedNonTransitive, ['external1-1.0.jar', 'two-1.0.jar']
+    checkDeps configurations.extendedBoth, ['external1-1.0.jar', 'one-1.0.jar']
+    checkDeps configurations.mergedNonTransitive, ['external1-1.0.jar', 'external1-1.0-classifier.jar']
+}
+"""
+        inTestDirectory().withTasks('test').run()
+    }
+
+    @Test
+    public void "configuration transitive = false overrides dependency transitive flag"() {
+        repo.module('org.gradle.test', 'one', '1.0').publish()
+        def module = repo.module('org.gradle.test', 'external1', '1.0')
+        module.dependsOn('org.gradle.test', 'one', '1.0')
+        module.publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    override { transitive = false }
+}
+dependencies {
+    override 'org.gradle.test:external1:1.0'
+}
+
+task test << {
+    assert configurations.override.collect { it.name } == ['external1-1.0.jar']
+}
+"""
+
+        inTestDirectory().withTasks('test').run()
+    }
+
+    /*
+     * Originally, we were aliasing dependency descriptors that were identical. This caused alias errors when we subsequently modified one of these descriptors.
+     */
+
+    @Test
+    public void addingClassifierToDuplicateDependencyDoesNotAffectOriginal() {
+        def module = repo.module('org.gradle.test', 'external1', '1.0')
+        module.artifact(classifier: 'withClassifier')
+        module.publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    a
+    b
+}
+dependencies {
+    a 'org.gradle.test:external1:1.0'
+    b 'org.gradle.test:external1:1.0', 'org.gradle.test:external1:1.0:withClassifier'
+}
+
+def checkDeps(config, expectedDependencies) {
+    assert config.collect({ it.name }) as Set == expectedDependencies as Set
+}
+
+task test << {
+    checkDeps configurations.a, ['external1-1.0.jar']
+    checkDeps configurations.b, ['external1-1.0-withClassifier.jar', 'external1-1.0.jar']
+}
+"""
+        inTestDirectory().withTasks('test').run()
+    }
+
+    @Test
+    public void reportsUnknownDependencyError() {
+        File buildFile = testFile("projectWithUnknownDependency.gradle");
+        ExecutionFailure failure = usingBuildFile(buildFile).runWithFailure();
+        failure.assertHasFileName("Build file '" + buildFile.getPath() + "'");
+        failure.assertHasDescription("Execution failed for task ':listJars'");
+        failure.assertThatCause(startsWith("Could not resolve all dependencies for configuration ':compile'"));
+        failure.assertThatCause(containsString("Could not find test:unknownProjectA:1.2."));
+        failure.assertThatCause(containsString("Could not find test:unknownProjectB:2.1.5."));
+    }
+
+    @Test
+    public void projectCanDependOnItself() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile << '''
+            configurations { compile; add('default') }
+            dependencies { compile project(':') }
+            task jar1(type: Jar) { destinationDir = buildDir; baseName = '1' }
+            task jar2(type: Jar) { destinationDir = buildDir; baseName = '2' }
+            artifacts { compile jar1; 'default' jar2 }
+            task listJars << {
+                assert configurations.compile.collect { it.name } == ['2.jar']
+            }
+'''
+
+        inTestDirectory().withTasks("listJars").run()
+    }
+
+    @Test
+    public void canSpecifyProducerTasksForFileDependency() {
+        testFile("settings.gradle").write("include 'sub'");
+        testFile("build.gradle") << '''
+            configurations { compile }
+            dependencies { compile project(path: ':sub', configuration: 'compile') }
+            task test(dependsOn: configurations.compile) << {
+                assert file('sub/sub.jar').isFile()
+            }
+'''
+        testFile("sub/build.gradle") << '''
+            configurations { compile }
+            dependencies { compile files('sub.jar') { builtBy 'jar' } }
+            task jar << { file('sub.jar').text = 'content' }
+'''
+
+        inTestDirectory().withTasks("test").run().assertTasksExecuted(":sub:jar", ":test");
+    }
+
+    def getRepo() {
+        return maven(testFile('repo'))
+    }
+}
+
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..4f73e39
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.test.fixtures.maven.MavenHttpModule
+import org.junit.Rule
+
+class ArtifactOnlyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+    @Rule public final TestResources resources = new TestResources();
+
+    MavenHttpModule projectA
+
+    def "setup"() {
+        projectA = mavenHttpRepo.module('group', 'projectA').publish()
+        server.start()
+
+        buildFile << """
+repositories {
+    maven { url '${mavenHttpRepo.uri}' }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.0 at jar'
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+    }
+
+    def "can resolve and cache artifact-only dependencies from a HTTP repository"() {
+        when:
+        projectA.pom.expectGet()
+        projectA.artifact.expectGet()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.0.jar')
+
+        and:
+        newRequestForModuleDoesNotContactServer()
+    }
+
+    def "can resolve and cache artifact-only dependencies from a HTTP repository with no descriptor"() {
+        when:
+        projectA.pom.expectGetMissing()
+        projectA.artifact.expectHead()
+        projectA.artifact.expectGet()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.0.jar')
+
+        and:
+        newRequestForModuleDoesNotContactServer()
+    }
+
+    private newRequestForModuleDoesNotContactServer() {
+        def snapshot = file('libs/projectA-1.0.jar').snapshot()
+        server.resetExpectations()
+        def result = run 'retrieve'
+        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
+        return result
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy
new file mode 100644
index 0000000..13af454
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.test.fixtures.file.TestFile;
+
+
+public class CacheResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    public void "cache handles manual deletion of cached artifacts"() {
+        server.start()
+
+        given:
+        def module = ivyHttpRepo.module('group', 'projectA', '1.2').publish()
+
+        def cacheDir = new TestFile(executer.gradleUserHomeDir).file('caches').toURI()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "${ivyHttpRepo.uri}" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+task deleteCacheFiles(type: Delete) {
+    delete fileTree(dir: '${cacheDir}', includes: ['**/projectA/**'])
+}
+"""
+
+        and:
+        module.allowAll()
+
+        and:
+        succeeds('listJars')
+        succeeds('deleteCacheFiles')
+        
+        when:
+        server.resetExpectations()
+        module.expectIvyGet()
+        module.expectJarGet()
+
+        then:
+        succeeds('listJars')
+    }
+
+    public void "cache entries are segregated between different repositories"() {
+        server.start()
+        given:
+        def repo1 = ivyHttpRepo('ivy-repo-a')
+        def module1 = repo1.module('org.gradle', 'testproject', '1.0').publish()
+        def repo2 = ivyHttpRepo('ivy-repo-b')
+        def module2 = repo2.module('org.gradle', 'testproject', '1.0').publishWithChangedContent()
+
+        and:
+        settingsFile << "include 'a','b'"
+        buildFile << """
+subprojects {
+    configurations {
+        test
+    }
+    dependencies {
+        test "org.gradle:testproject:1.0"
+    }
+    task retrieve(type: Sync) {
+        into 'build'
+        from configurations.test
+    }
+}
+project('a') {
+    repositories {
+        ivy { url "${repo1.uri}" }
+    }
+}
+project('b') {
+    repositories {
+        ivy { url "${repo2.uri}" }
+    }
+    retrieve.dependsOn(':a:retrieve')
+}
+"""
+
+        when:
+        module1.expectIvyGet()
+        module1.expectJarGet()
+
+        module2.expectIvyHead()
+        module2.expectIvySha1Get()
+        module2.expectIvyGet()
+        module2.expectJarHead()
+        module2.expectJarSha1Get()
+        module2.expectJarGet()
+
+        then:
+        succeeds 'retrieve'
+
+        and:
+        file('a/build/testproject-1.0.jar').assertIsCopyOf(module1.jarFile)
+        file('b/build/testproject-1.0.jar').assertIsCopyOf(module2.jarFile)
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ClientModuleDependenciesResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ClientModuleDependenciesResolveIntegrationTest.groovy
new file mode 100644
index 0000000..b347c86
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ClientModuleDependenciesResolveIntegrationTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.junit.Test
+
+/**
+ * @author Hans Dockter
+ */
+public class ClientModuleDependenciesResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    @Test
+    public void "uses metadata from Client Module and looks up artifact in declared repositories"() {
+        given:
+        def repo1 = ivyHttpRepo("repo1")
+        def repo2 = ivyHttpRepo("repo2")
+        def projectAInRepo1 = repo1.module('group', 'projectA', '1.2')
+        def projectAInRepo2 = repo2.module('group', 'projectA', '1.2').publish()
+        def projectB = repo1.module('group', 'projectB', '1.3').publish()
+
+        server.start()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "${repo1.uri}" }
+    ivy { url "${repo2.uri}" }
+}
+configurations { compile }
+dependencies {
+    compile module("group:projectA:1.2") {
+       dependency("group:projectB:1.3")
+    }
+}
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar', 'projectB-1.3.jar']
+}
+"""
+
+        when:
+        projectB.expectIvyGet()
+        projectB.expectJarGet()
+        projectAInRepo1.expectIvyGetMissing()
+        projectAInRepo1.expectJarHeadMissing()
+        projectAInRepo2.expectIvyGet()
+        projectAInRepo2.expectJarGet()
+
+        then:
+        succeeds('listJars')
+
+        when:
+        server.resetExpectations()
+
+        then:
+        succeeds('listJars')
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependenciesResolveIntegrationTest.java b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependenciesResolveIntegrationTest.java
new file mode 100644
index 0000000..241d8bb
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependenciesResolveIntegrationTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve;
+
+import org.gradle.integtests.fixtures.Sample;
+import org.gradle.integtests.fixtures.executer.GradleContextualExecuter;
+import org.gradle.integtests.fixtures.executer.GradleDistribution;
+import org.gradle.integtests.fixtures.executer.GradleExecuter;
+import org.gradle.integtests.fixtures.executer.UnderDevelopmentGradleDistribution;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class DependenciesResolveIntegrationTest {
+    @Rule public final TestNameTestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider();
+    private final GradleDistribution dist = new UnderDevelopmentGradleDistribution();
+    private final GradleExecuter executer = new GradleContextualExecuter(dist, testDirectoryProvider);
+    @Rule public final Sample sample = new Sample("dependencies");
+
+    @Test
+    public void testResolve() {
+        executer.requireOwnGradleUserHomeDir();
+
+        // the actual testing is done in the build script.
+        File projectDir = sample.getDir();
+        executer.inDirectory(projectDir).withTasks("test").run();
+    }   
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyNotationIntegrationSpec.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyNotationIntegrationSpec.groovy
new file mode 100644
index 0000000..f2c2a12
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyNotationIntegrationSpec.groovy
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.resolve
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.hamcrest.Matchers
+
+/**
+ * by Szczepan Faber, created at: 11/9/11
+ */
+class DependencyNotationIntegrationSpec extends AbstractIntegrationSpec {
+
+    def "understands dependency notations"() {
+        when:
+        buildFile <<  """
+import org.gradle.api.internal.artifacts.dependencies.*
+configurations {
+    conf
+    gradleStuff
+    allowsCollections
+}
+
+def someDependency = new DefaultSelfResolvingDependency(files('foo.txt'))
+dependencies {
+    conf someDependency
+    conf "org.mockito:mockito-core:1.8"
+    conf group: 'org.spockframework', name: 'spock-core', version: '1.0'
+    conf module('org.foo:moduleOne:1.0'), module('org.foo:moduleTwo:1.0')
+
+    gradleStuff gradleApi()
+
+    allowsCollections "org.mockito:mockito-core:1.8", someDependency
+}
+
+task checkDeps << {
+    def deps = configurations.conf.incoming.dependencies
+    assert deps.contains(someDependency)
+    assert deps.find { it instanceof ExternalDependency && it.group == 'org.mockito' && it.name == 'mockito-core' && it.version == '1.8'  }
+    assert deps.find { it instanceof ExternalDependency && it.group == 'org.spockframework' && it.name == 'spock-core' && it.version == '1.0'  }
+    assert deps.find { it instanceof ClientModule && it.name == 'moduleOne' && it.group == 'org.foo' }
+    assert deps.find { it instanceof ClientModule && it.name == 'moduleTwo' && it.version == '1.0' }
+
+    deps = configurations.gradleStuff.dependencies
+    assert deps.findAll { it instanceof SelfResolvingDependency }.size() > 0 : "should include gradle api jars"
+
+    deps = configurations.allowsCollections.dependencies
+    assert deps.size() == 2
+    assert deps.find { it instanceof ExternalDependency && it.group == 'org.mockito' }
+    assert deps.contains(someDependency)
+}
+"""
+        then:
+        succeeds 'checkDeps'
+    }
+
+    def "understands project notations"() {
+        when:
+        settingsFile << "include 'otherProject'"
+
+        buildFile <<  """
+configurations {
+    conf
+    confTwo
+}
+
+project(':otherProject') {
+    configurations {
+        otherConf
+    }
+}
+
+dependencies {
+    conf project(':otherProject')
+    confTwo project(path: ':otherProject', configuration: 'otherConf')
+}
+
+task checkDeps << {
+    def deps = configurations.conf.incoming.dependencies
+    assert deps.size() == 1
+    assert deps.find { it.dependencyProject.path == ':otherProject' }
+
+    deps = configurations.confTwo.incoming.dependencies
+    assert deps.size() == 1
+    assert deps.find { it.dependencyProject.path == ':otherProject' && it.projectConfiguration.name == 'otherConf' }
+}
+"""
+        then:
+        succeeds 'checkDeps'
+    }
+
+    def "understands client module notation with dependencies"() {
+        when:
+        buildFile <<  """
+configurations {
+    conf
+}
+
+dependencies {
+    conf module('org.foo:moduleOne:1.0') {
+        dependency 'org.foo:bar:1.0'
+        dependencies ('org.foo:one:1', 'org.foo:two:1')
+        dependency ('high:five:5') { transitive = false }
+    }
+}
+
+task checkDeps << {
+    def deps = configurations.conf.incoming.dependencies
+    assert deps.size() == 1
+    def dep = deps.find { it instanceof ClientModule && it.name == 'moduleOne' }
+    assert dep
+    assert dep.dependencies.size() == 4
+    assert dep.dependencies.find { it.group == 'org.foo' && it.name == 'bar' && it.version == '1.0' && it.transitive == true }
+    assert dep.dependencies.find { it.group == 'org.foo' && it.name == 'one' && it.version == '1' }
+    assert dep.dependencies.find { it.group == 'org.foo' && it.name == 'two' && it.version == '1' }
+    assert dep.dependencies.find { it.group == 'high' && it.name == 'five' && it.version == '5' && it.transitive == false }
+}
+"""
+        then:
+        succeeds 'checkDeps'
+    }
+
+    def "fails gracefully for invalid notations"() {
+        when:
+        buildFile <<  """
+configurations {
+    conf
+}
+
+dependencies {
+    conf 100
+}
+
+task checkDeps
+"""
+        then:
+        fails 'checkDeps'
+        failure.assertThatCause(Matchers.startsWith("Cannot convert the provided notation to an object of type Dependency: 100."))
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolutionEventsIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolutionEventsIntegrationTest.groovy
new file mode 100644
index 0000000..fc7881e
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolutionEventsIntegrationTest.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve
+
+import org.gradle.integtests.fixtures.*
+import spock.lang.Issue
+
+class DependencyResolutionEventsIntegrationTest extends AbstractIntegrationSpec {
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2047")
+    def "can access resolved files from afterResolve hook"() {
+        given:
+        file("thing.txt") << "stuff"
+        buildFile << """
+            configurations {
+                things.incoming.afterResolve { incoming ->
+                    incoming.files.files
+                    println "accessed files"
+                }
+            }
+            dependencies {
+                things files("thing.txt")
+            }
+
+            task resolveIt(type: Copy, dependsOn: configurations.things) {
+                from configurations.things
+                into buildDir
+            }
+        """
+
+        when:
+        run "resolveIt"
+
+        then:
+        output.contains "accessed files"
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolveRulesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolveRulesIntegrationTest.groovy
new file mode 100644
index 0000000..eaebb35
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolveRulesIntegrationTest.groovy
@@ -0,0 +1,599 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.integtests.resolve
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+/**
+ * @author Szczepan Faber, @date 03.03.11
+ */
+class DependencyResolveRulesIntegrationTest extends AbstractIntegrationSpec {
+
+    void "forces multiple modules by rule"()
+    {
+        mavenRepo.module("org.utils", "impl", '1.3').dependsOn('org.utils', 'api', '1.3').publish()
+        mavenRepo.module("org.utils", "impl", '1.5').dependsOn('org.utils', 'api', '1.5').publish()
+
+        mavenRepo.module("org.utils", "api", '1.3').publish()
+        mavenRepo.module("org.utils", "api", '1.5').publish()
+
+        mavenRepo.module("org.stuff", "foo", '2.0').dependsOn('org.utils', 'api', '1.5') publish()
+        mavenRepo.module("org.utils", "optional-lib", '5.0').publish()
+
+        //above models the scenario where org.utils:api and org.utils:impl are libraries that must be resolved with the same version
+        //however due to the conflict resolution, org.utils:api:1.5 and org.utils.impl:1.3 are resolved.
+
+        buildFile << """
+            $common
+
+            dependencies {
+                conf 'org.stuff:foo:2.0', 'org.utils:impl:1.3', 'org.utils:optional-lib:5.0'
+            }
+
+            configurations.conf.resolutionStrategy {
+	            eachDependency {
+                    if (it.requested.group == 'org.utils' && it.requested.name != 'optional-lib') {
+                        it.useVersion '1.5'
+                    }
+	            }
+	            failOnVersionConflict()
+	        }
+"""
+
+        when:
+        run("resolveConf")
+
+        then:
+        noExceptionThrown()
+    }
+
+    void "module forced by rule has correct selection reason"()
+    {
+        mavenRepo.module("org.utils", "impl", '1.3').dependsOn('org.utils', 'api', '1.3').publish()
+        mavenRepo.module("org.utils", "impl", '1.5').dependsOn('org.utils', 'api', '1.5').publish()
+
+        mavenRepo.module("org.utils", "api", '1.3').publish()
+        mavenRepo.module("org.utils", "api", '1.5').publish()
+
+        mavenRepo.module("org.stuff", "foo", '2.0').dependsOn('org.utils', 'impl', '1.3') publish()
+
+        buildFile << """
+            $common
+
+            dependencies {
+                conf 'org.stuff:foo:2.0'
+            }
+
+            configurations.conf.resolutionStrategy {
+	            eachDependency {
+                    if (it.requested.group == 'org.utils') {
+                        it.useVersion '1.5'
+                    }
+	            }
+	        }
+
+	        task check << {
+	            def deps = configurations.conf.incoming.resolutionResult.allDependencies
+	            assert deps*.selected.id.name == ['foo', 'impl', 'api']
+	            assert deps*.selected.id.version == ['2.0', '1.5', '1.5']
+	            assert deps*.selected.selectionReason.forced         == [false, false, false]
+	            assert deps*.selected.selectionReason.selectedByRule == [false, true, true]
+	        }
+"""
+
+        when:
+        run("check")
+
+        then:
+        noExceptionThrown()
+    }
+
+    void "all rules are executed orderly and last one wins"()
+    {
+        mavenRepo.module("org.utils", "impl", '1.3').dependsOn('org.utils', 'api', '1.3').publish()
+        mavenRepo.module("org.utils", "impl", '1.5').dependsOn('org.utils', 'api', '1.5').publish()
+
+        mavenRepo.module("org.utils", "api", '1.3').publish()
+        mavenRepo.module("org.utils", "api", '1.5').publish()
+
+        buildFile << """
+            $common
+
+            dependencies {
+                conf 'org.utils:impl:1.3'
+            }
+
+            configurations.conf.resolutionStrategy {
+	            eachDependency {
+	                assert it.target == it.requested
+                    it.useVersion '1.4'
+	            }
+	            eachDependency {
+	                assert it.target.version == '1.4'
+	                assert it.target.name == it.requested.name
+	                assert it.target.group == it.requested.group
+                    it.useVersion '1.5'
+	            }
+	            eachDependency {
+	                assert it.target.version == '1.5'
+	                //don't change the version
+	            }
+	        }
+
+	        task check << {
+	            def deps = configurations.conf.incoming.resolutionResult.allDependencies
+                assert deps.size() == 2
+                deps.each {
+	                assert it.selected.id.version == '1.5'
+	                assert it.selected.selectionReason.selectedByRule
+	                assert it.selected.selectionReason.description == 'selected by rule'
+	            }
+	        }
+"""
+
+        when:
+        run("check")
+
+        then:
+        noExceptionThrown()
+    }
+
+    void "can unforce the version"()
+    {
+        mavenRepo.module("org.utils", "impl", '1.3').dependsOn('org.utils', 'api', '1.3').publish()
+        mavenRepo.module("org.utils", "impl", '1.5').dependsOn('org.utils', 'api', '1.5').publish()
+
+        mavenRepo.module("org.utils", "api", '1.3').publish()
+        mavenRepo.module("org.utils", "api", '1.5').publish()
+
+        buildFile << """
+            $common
+
+            dependencies {
+                conf 'org.utils:impl:1.3'
+            }
+
+            configurations.conf.resolutionStrategy {
+                force("org.utils:impl:1.5", "org.utils:api:1.5")
+
+	            eachDependency {
+                    it.useVersion it.requested.version
+	            }
+	        }
+
+	        task check << {
+	            def deps = configurations.conf.incoming.resolutionResult.allDependencies
+                assert deps.size() == 2
+                deps.each {
+	                assert it.selected.id.version == '1.3'
+                    def reason = it.selected.selectionReason
+                    assert !reason.forced
+                    assert reason.selectedByRule
+	            }
+	        }
+"""
+
+        when:
+        run("check")
+
+        then:
+        noExceptionThrown()
+    }
+
+    void "rule are applied after forced modules"()
+    {
+        mavenRepo.module("org.utils", "impl", '1.3').dependsOn('org.utils', 'api', '1.3').publish()
+        mavenRepo.module("org.utils", "impl", '1.5').dependsOn('org.utils', 'api', '1.5').publish()
+
+        mavenRepo.module("org.utils", "api", '1.3').publish()
+        mavenRepo.module("org.utils", "api", '1.5').publish()
+
+        buildFile << """
+            $common
+
+            dependencies {
+                conf 'org.utils:impl:1.3'
+            }
+
+            configurations.conf.resolutionStrategy {
+                force("org.utils:impl:1.5", "org.utils:api:1.5")
+
+	            eachDependency {
+                    assert it.target.version == '1.5'
+                    it.useVersion '1.3'
+	            }
+	        }
+
+	        task check << {
+	            def deps = configurations.conf.incoming.resolutionResult.allDependencies
+                assert deps.size() == 2
+                deps.each {
+	                assert it.selected.id.version == '1.3'
+                    def reason = it.selected.selectionReason
+                    assert !reason.forced
+                    assert reason.selectedByRule
+	            }
+	        }
+"""
+
+        when:
+        run("check")
+
+        then:
+        noExceptionThrown()
+    }
+
+    void "forced modules and rules coexist"()
+    {
+        mavenRepo.module("org.utils", "impl", '1.3').dependsOn('org.utils', 'api', '1.3').publish()
+        mavenRepo.module("org.utils", "impl", '1.5').dependsOn('org.utils', 'api', '1.5').publish()
+
+        mavenRepo.module("org.utils", "api", '1.3').publish()
+        mavenRepo.module("org.utils", "api", '1.5').publish()
+
+        buildFile << """
+            $common
+
+            dependencies {
+                conf 'org.utils:impl:1.3'
+            }
+
+            configurations.conf.resolutionStrategy {
+                force("org.utils:impl:1.5")
+
+	            eachDependency {
+                    if (it.requested.name == 'api') {
+                        assert it.target == it.requested
+                        it.useVersion '1.5'
+                    }
+	            }
+	        }
+
+	        task check << {
+                def deps = configurations.conf.incoming.resolutionResult.allDependencies
+                assert deps.find {
+                    it.selected.id.name == 'impl' &&
+                    it.selected.id.version == '1.5' &&
+                    it.selected.selectionReason.forced &&
+                    !it.selected.selectionReason.selectedByRule
+                }
+
+                assert deps.find {
+	                it.selected.id.name == 'api' &&
+                    it.selected.id.version == '1.5' &&
+                    !it.selected.selectionReason.forced &&
+                    it.selected.selectionReason.selectedByRule
+	            }
+	        }
+"""
+
+        when:
+        run("check")
+
+        then:
+        noExceptionThrown()
+    }
+
+    void "rule selects a dynamic version"()
+    {
+        mavenRepo.module("org.utils", "api", '1.3').publish()
+        mavenRepo.module("org.utils", "api", '1.4').publish()
+        mavenRepo.module("org.utils", "api", '1.5').publish()
+
+        buildFile << """
+            $common
+
+            dependencies {
+                conf 'org.utils:api:1.3'
+            }
+
+            configurations.conf.resolutionStrategy.eachDependency {
+                it.useVersion '1.+'
+	        }
+
+	        task check << {
+                def deps = configurations.conf.incoming.resolutionResult.allDependencies as List
+                assert deps.size() == 1
+                assert deps[0].requested.version == '1.3'
+                assert deps[0].selected.id.version == '1.5'
+                assert !deps[0].selected.selectionReason.forced
+                assert deps[0].selected.selectionReason.selectedByRule
+	        }
+"""
+
+        when:
+        run("check")
+
+        then:
+        noExceptionThrown()
+    }
+
+    void "can blacklist a version"()
+    {
+        mavenRepo.module("org.utils", "a",  '1.4').publish()
+        mavenRepo.module("org.utils", "a",  '1.3').publish()
+        mavenRepo.module("org.utils", "a",  '1.2').publish()
+        mavenRepo.module("org.utils", "b", '1.3').dependsOn("org.utils", "a", "1.3").publish()
+
+        buildFile << """
+            $common
+
+            dependencies {
+                conf 'org.utils:a:1.2', 'org.utils:b:1.3'
+            }
+
+            configurations.conf.resolutionStrategy.eachDependency {
+                // a:1.2 is blacklisted, 1.4 should be used instead:
+                if (it.requested.name == 'a' && it.requested.version == '1.2') {
+                    it.useVersion '1.4'
+                }
+	        }
+
+	        task check << {
+                def modules = configurations.conf.incoming.resolutionResult.allModuleVersions as List
+                def a = modules.find { it.id.name == 'a' }
+                assert a.id.version == '1.4'
+                assert a.selectionReason.conflictResolution
+                assert a.selectionReason.selectedByRule
+                assert !a.selectionReason.forced
+                assert a.selectionReason.description == 'conflict resolution by rule'
+	        }
+"""
+
+        when:
+        run("check")
+
+        then:
+        noExceptionThrown()
+    }
+
+    void "can blacklist a version that is not used"()
+    {
+        mavenRepo.module("org.utils", "a",  '1.3').publish()
+        mavenRepo.module("org.utils", "a",  '1.2').publish()
+        mavenRepo.module("org.utils", "b", '1.3').dependsOn("org.utils", "a", "1.3").publish()
+
+        buildFile << """
+            $common
+
+            dependencies {
+                conf 'org.utils:a:1.2', 'org.utils:b:1.3'
+            }
+
+            configurations.conf.resolutionStrategy.eachDependency {
+                // a:1.2 is blacklisted, 1.2.1 should be used instead:
+                if (it.requested.name == 'a' && it.requested.version == '1.2') {
+                    it.useVersion '1.2.1'
+                }
+	        }
+
+	        task check << {
+                def modules = configurations.conf.incoming.resolutionResult.allModuleVersions as List
+                def a = modules.find { it.id.name == 'a' }
+                assert a.id.version == '1.3'
+                assert a.selectionReason.conflictResolution
+                assert !a.selectionReason.selectedByRule
+                assert !a.selectionReason.forced
+	        }
+"""
+
+        when:
+        run("check")
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "can use custom versioning scheme"()
+    {
+        mavenRepo.module("org.utils", "api",  '1.3').publish()
+
+        buildFile << """
+            $common
+
+            dependencies {
+                conf 'org.utils:api:default'
+            }
+
+            configurations.conf.resolutionStrategy.eachDependency {
+                if (it.requested.version == 'default') {
+                    it.useVersion '1.3'
+                }
+	        }
+
+	        task check << {
+                def deps = configurations.conf.incoming.resolutionResult.allDependencies as List
+                assert deps.size() == 1
+                deps[0].requested.version == 'default'
+                deps[0].selected.id.version == '1.3'
+                deps[0].selected.selectionReason.selectedByRule
+	        }
+"""
+
+        when:
+        run("check")
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "can use custom versioning scheme for transitive dependencies"()
+    {
+        mavenRepo.module("org.utils", "api",  '1.3').publish()
+        mavenRepo.module("org.utils", "impl", '1.3').dependsOn('org.utils', 'api', 'default').publish()
+
+        buildFile << """
+            $common
+
+            dependencies {
+                conf 'org.utils:impl:1.3'
+            }
+
+            configurations.conf.resolutionStrategy.eachDependency {
+                if (it.requested.version == 'default') {
+                    it.useVersion '1.3'
+                }
+	        }
+
+	        task check << {
+                def deps = configurations.conf.incoming.resolutionResult.allDependencies as List
+                assert deps.size() == 2
+                def api = deps.find { it.requested.name == 'api' }
+                api.requested.version == 'default'
+                api.selected.id.version == '1.3'
+                api.selected.selectionReason.selectedByRule
+	        }
+"""
+
+        when:
+        run("check")
+
+        then:
+        noExceptionThrown()
+    }
+
+    void "rule selects unavailable version"()
+    {
+        mavenRepo.module("org.utils", "api", '1.3').publish()
+
+        buildFile << """
+            $common
+
+            dependencies {
+                conf 'org.utils:api:1.3'
+            }
+
+            configurations.conf.resolutionStrategy.eachDependency {
+                it.useVersion '1.123.15' //does not exist
+	        }
+
+	        task check << {
+                def deps = configurations.conf.incoming.resolutionResult.allDependencies as List
+                assert deps.size() == 1
+                assert deps[0].failure
+                assert deps[0].failure.message.contains('1.123.15')
+                assert deps[0].requested.version == '1.3'
+	        }
+"""
+
+        when:
+        def failure = runAndFail("check", "resolveConf")
+
+        then:
+        failure.dependencyResolutionFailure
+            .assertFailedConfiguration(":conf")
+            .assertHasCause("Could not find org.utils:api:1.123.15")
+    }
+
+    void "rules triggered exactly once per the same dependency"()
+    {
+        mavenRepo.module("org.utils", "impl", '1.3').dependsOn('org.utils', 'api', '1.3').publish()
+        mavenRepo.module("org.utils", "api", '1.3').publish()
+
+        mavenRepo.module("org.utils", "impl", '1.3').dependsOn('org.utils', 'api', '1.3').publish()
+        mavenRepo.module("org.utils", "api", '1.5').publish()
+
+        mavenRepo.module("org.stuff", "foo", '2.0').dependsOn('org.utils', 'api', '1.5').publish()
+        mavenRepo.module("org.stuff", "bar", '2.0').dependsOn('org.utils', 'impl', '1.3').publish()
+
+        /*
+        dependencies:
+
+        impl:1.3->api:1.3
+        foo->api:1.5
+        bar->impl:1.3(*)->api:1.3(*)
+
+        * - should be excluded as it was already visited
+        */
+
+        buildFile << """
+            $common
+
+            dependencies {
+                conf 'org.utils:impl:1.3', 'org.stuff:foo:2.0', 'org.stuff:bar:2.0'
+            }
+
+            List requested = []
+
+            configurations.conf.resolutionStrategy {
+	            eachDependency {
+                    requested << "\$it.requested.name:\$it.requested.version"
+	            }
+	        }
+
+	        task check << {
+                configurations.conf.resolve()
+                assert requested == ['impl:1.3', 'foo:2.0', 'bar:2.0', 'api:1.3', 'api:1.5']
+	        }
+"""
+
+        when:
+        run("check")
+
+        then:
+        noExceptionThrown()
+    }
+
+    void "runtime exception when evaluating rule yields decent exception"()
+    {
+        mavenRepo.module("org.utils", "impl", '1.3').dependsOn('org.utils', 'api', '1.3').publish()
+        mavenRepo.module("org.utils", "api", '1.3').publish()
+
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            version = 1.0
+
+            $common
+
+            dependencies {
+                conf 'org.utils:impl:1.3'
+            }
+
+            configurations.conf.resolutionStrategy {
+	            eachDependency {
+                    it.useVersion '1.3' //happy
+	            }
+                eachDependency {
+                    throw new RuntimeException("Unhappy :(")
+	            }
+	        }
+"""
+
+        when:
+        def failure = runAndFail("resolveConf")
+
+        then:
+        failure.dependencyResolutionFailure
+                .assertFailedConfiguration(":conf")
+                .assertHasCause("Could not resolve org.utils:impl:1.3.")
+                .assertHasCause("Unhappy :(")
+                .assertFailedDependencyRequiredBy(":root:1.0")
+    }
+
+    String getCommon() {
+        """configurations { conf }
+        repositories {
+            maven { url "${mavenRepo.uri}" }
+        }
+        task resolveConf << { configurations.conf.files }
+
+        //resolving the configuration at the end:
+        gradle.startParameter.taskNames += 'resolveConf'
+        """
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/FlatDirResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/FlatDirResolveIntegrationTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/FlatDirResolveIntegrationTest.groovy
rename to subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/FlatDirResolveIntegrationTest.groovy
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy
new file mode 100644
index 0000000..a7f7f2c
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class ProjectDependencyResolveIntegrationTest extends AbstractIntegrationSpec {
+    public void "project dependency includes artifacts and transitive dependencies of default configuration in target project"() {
+        given:
+        mavenRepo.module("org.other", "externalA", 1.2).publish()
+        mavenRepo.module("org.other", "externalB", 2.1).publish()
+
+        and:
+        file('settings.gradle') << "include 'a', 'b'"
+
+        and:
+        buildFile << """
+allprojects {
+    repositories { maven { url '$mavenRepo.uri' } }
+}
+project(":a") {
+    configurations {
+        api
+        'default' { extendsFrom api }
+    }
+    dependencies {
+        api "org.other:externalA:1.2"
+        'default' "org.other:externalB:2.1"
+    }
+    task jar(type: Jar) { baseName = 'a' }
+    artifacts { api jar }
+}
+project(":b") {
+    configurations {
+        compile
+    }
+    dependencies {
+        compile project(':a')
+    }
+    task check(dependsOn: configurations.compile) << {
+        assert configurations.compile.collect { it.name } == ['a.jar', 'externalA-1.2.jar', 'externalB-2.1.jar']
+    }
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    public void "project dependency that specifies a target configuration includes artifacts and transitive dependencies of selected configuration"() {
+        given:
+        mavenRepo.module("org.other", "externalA", 1.2).publish()
+
+        and:
+        file('settings.gradle') << "include 'a', 'b'"
+
+        and:
+        buildFile << """
+allprojects {
+    repositories { maven { url '$mavenRepo.uri' } }
+}
+project(":a") {
+    configurations {
+        api
+        runtime { extendsFrom api }
+    }
+    dependencies {
+        api "org.other:externalA:1.2"
+    }
+    task jar(type: Jar) { baseName = 'a' }
+    artifacts { api jar }
+}
+project(":b") {
+    configurations {
+        compile
+    }
+    dependencies {
+        compile project(path: ':a', configuration: 'runtime')
+    }
+    task check(dependsOn: configurations.compile) << {
+        assert configurations.compile.collect { it.name } == ['a.jar', 'externalA-1.2.jar']
+    }
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    public void "resolved project artifacts contain project version in their names"() {
+        given:
+        file('settings.gradle') << "include 'a', 'b'"
+
+        and:
+        file('a/build.gradle') << '''
+            apply plugin: 'base'
+            configurations { compile }
+            task aJar(type: Jar) { }
+            artifacts { compile aJar }
+'''
+        file('b/build.gradle') << '''
+            apply plugin: 'base'
+            version = 'early'
+            configurations { compile }
+            task bJar(type: Jar) { }
+            gradle.taskGraph.whenReady { project.version = 'late' }
+            artifacts { compile bJar }
+'''
+        file('build.gradle') << '''
+            configurations { compile }
+            dependencies { compile project(path: ':a', configuration: 'compile'), project(path: ':b', configuration: 'compile') }
+            task test(dependsOn: configurations.compile) << {
+                assert configurations.compile.collect { it.name } == ['a.jar', 'b-late.jar']
+            }
+'''
+
+        expect:
+        succeeds "test"
+    }
+
+    public void "project dependency that references an artifact includes the matching artifact only plus the transitive dependencies of referenced configuration"() {
+        given:
+        mavenRepo.module("group", "externalA", 1.5).publish()
+
+        and:
+        file('settings.gradle') << "include 'a', 'b'"
+
+        and:
+        buildFile << """
+allprojects {
+    apply plugin: 'base'
+    repositories { maven { url '${mavenRepo.uri}' } }
+}
+
+project(":a") {
+    configurations { 'default' {} }
+    dependencies { 'default' 'group:externalA:1.5' }
+    task aJar(type: Jar) { }
+    artifacts { 'default' aJar }
+}
+
+project(":b") {
+    configurations { compile }
+    dependencies { compile(project(':a')) { artifact { name = 'a'; type = 'jar' } } }
+    task test {
+        inputs.files configurations.compile
+        doFirst {
+            assert configurations.compile.files.collect { it.name } == ['a.jar', 'externalA-1.5.jar']
+        }
+    }
+}
+"""
+
+        expect:
+        succeeds 'test'
+    }
+
+    public void "non-transitive project dependency includes only the artifacts of the target configuration"() {
+        given:
+        mavenRepo.module("group", "externalA", 1.5).publish()
+
+        and:
+        file('settings.gradle') << "include 'a', 'b'"
+
+        and:
+        buildFile << '''
+allprojects {
+    apply plugin: 'java'
+    repositories { maven { url rootProject.uri('repo') } }
+}
+project(':a') {
+    dependencies {
+        compile 'group:externalA:1.5'
+        compile files('libs/externalB.jar')
+    }
+}
+project(':b') {
+    dependencies {
+        compile project(':a'), { transitive = false }
+    }
+    task listJars << {
+        assert configurations.compile.collect { it.name } == ['a.jar']
+    }
+}
+'''
+
+        expect:
+        succeeds "listJars"
+    }
+
+    public void "can have cycle in project dependencies"() {
+        given:
+        file('settings.gradle') << "include 'a', 'b', 'c'"
+
+        and:
+        buildFile << """
+
+subprojects {
+    apply plugin: 'base'
+    configurations {
+        'default'
+        other
+    }
+    task jar(type: Jar)
+    artifacts {
+        'default' jar
+    }
+}
+
+project('a') {
+    dependencies {
+        'default' project(':b')
+        other project(':b')
+    }
+    task listJars {
+        dependsOn configurations.default
+        dependsOn configurations.other
+        doFirst {
+            def jars = configurations.default.collect { it.name } as Set
+            assert jars == ['a.jar', 'b.jar', 'c.jar'] as Set
+
+            jars = configurations.other.collect { it.name } as Set
+            assert jars == ['a.jar', 'b.jar', 'c.jar'] as Set
+        }
+    }
+}
+
+project('b') {
+    dependencies {
+        'default' project(':c')
+    }
+}
+
+project('c') {
+    dependencies {
+        'default' project(':a')
+    }
+}
+"""
+
+        expect:
+        succeeds "listJars"
+    }
+
+    // this test is largely covered by other tests, but does ensure that there is nothing special about
+    // project dependencies that are “built” by built in plugins like the Java plugin's created jars
+    def "can use zip files as project dependencies"() {
+        given:
+        file("settings.gradle") << "include 'a'; include 'b'"
+        file("a/some.txt") << "foo"
+        file("a/build.gradle") << """
+            group = "g"
+            version = 1.0
+            
+            apply plugin: 'base'
+            task zip(type: Zip) {
+                from "some.txt"
+            }
+
+            artifacts {
+                delegate.default zip
+            }
+        """
+        file("b/build.gradle") << """
+            configurations { conf }
+            dependencies {
+                conf project(":a")
+            }
+            
+            task copyZip(type: Copy) {
+                from configurations.conf
+                into "\$buildDir/copied"
+            }
+        """
+        
+        when:
+        succeeds ":b:copyZip"
+        
+        then:
+        ":b:copyZip" in nonSkippedTasks
+        
+        and:
+        file("b/build/copied/a-1.0.zip").exists()
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolutionStrategySamplesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolutionStrategySamplesIntegrationTest.groovy
new file mode 100644
index 0000000..3434139
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolutionStrategySamplesIntegrationTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.resolve
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.junit.Rule
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+/**
+ * @author Szczepan Faber, @date 03.03.11
+ */
+class ResolutionStrategySamplesIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule public final Sample sample = new Sample('userguide/artifacts/resolutionStrategy')
+
+    @Requires(TestPrecondition.NOT_JDK5)
+    //because some of the api of the samples does not work with JDK5, see GRADLE-1949
+    void "can resolve dependencies"()
+    {
+        sample.dir.file("build.gradle") << """
+            configurations { conf }
+            task resolveConf << { configurations.conf.files }
+        """
+
+        when:
+        inDirectory(sample.dir)
+        //smoke testing if dependency resolution works fine
+        run("resolveConf")
+
+        then:
+        noExceptionThrown()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ResolveCrossVersionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolveCrossVersionIntegrationTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ResolveCrossVersionIntegrationTest.groovy
rename to subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolveCrossVersionIntegrationTest.groovy
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy
rename to subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/VersionConflictResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/VersionConflictResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..f166133
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/VersionConflictResolutionIntegrationTest.groovy
@@ -0,0 +1,817 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve
+
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.junit.Test
+import spock.lang.Issue
+
+import static org.hamcrest.Matchers.containsString
+
+/**
+ * @author Szczepan Faber, @date 03.03.11
+ */
+class VersionConflictResolutionIntegrationTest extends AbstractIntegrationTest {
+
+    @Test
+    void "strict conflict resolution should fail due to conflict"() {
+        repo.module("org", "foo", '1.3.3').publish()
+        repo.module("org", "foo", '1.4.4').publish()
+
+        def settingsFile = file("settings.gradle")
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories {
+		maven { url "${repo.uri}" }
+	}
+}
+
+project(':api') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.4.4')
+	}
+}
+
+project(':tool') {
+	dependencies {
+		compile project(':api')
+		compile project(':impl')
+	}
+
+	configurations.compile.resolutionStrategy.failOnVersionConflict()
+}
+"""
+
+        //when
+        def result = executer.withTasks("tool:dependencies").runWithFailure()
+
+        //then
+        result.assertThatCause(containsString('A conflict was found between the following modules:'))
+    }
+
+    @Test
+    void "strict conflict resolution should pass when no conflicts"() {
+        repo.module("org", "foo", '1.3.3').publish()
+
+        def settingsFile = file("settings.gradle")
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories {
+		maven { url "${repo.uri}" }
+	}
+}
+
+project(':api') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':tool') {
+	dependencies {
+		compile project(':api')
+		compile project(':impl')
+	}
+
+	configurations.all { resolutionStrategy.failOnVersionConflict() }
+}
+"""
+
+        //when
+        executer.withTasks("tool:dependencies").run()
+
+        //then no exceptions are thrown
+    }
+
+    @Test
+    void "strict conflict strategy can be used with forced modules"() {
+        repo.module("org", "foo", '1.3.3').publish()
+        repo.module("org", "foo", '1.4.4').publish()
+        repo.module("org", "foo", '1.5.5').publish()
+
+        def settingsFile = file("settings.gradle")
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories {
+		maven { url "${repo.uri}" }
+	}
+}
+
+project(':api') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.4.4')
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':tool') {
+	dependencies {
+		compile project(':api')
+		compile project(':impl')
+		compile('org:foo:1.5.5'){
+		    force = true
+		}
+	}
+
+	configurations.all { resolutionStrategy.failOnVersionConflict() }
+}
+"""
+
+        //when
+        executer.withTasks("tool:dependencies").run()
+
+        //then no exceptions are thrown because we forced a certain version of conflicting dependency
+    }
+
+    @Test
+    void "can force already resolved version of a module and avoid conflict"() {
+        repo.module("org", "foo", '1.3.3').publish()
+        repo.module("org", "foo", '1.4.4').publish()
+
+        def settingsFile = file("settings.gradle")
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories {
+		maven { url "${repo.uri}" }
+	}
+}
+
+project(':api') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.4.4')
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':tool') {
+
+	dependencies {
+		compile project(':api')
+		compile project(':impl')
+	}
+}
+
+allprojects {
+    configurations.all {
+	    resolutionStrategy {
+	        force 'org:foo:1.3.3'
+	        failOnVersionConflict()
+	    }
+	}
+}
+
+"""
+
+        //when
+        executer.withTasks("api:dependencies", "tool:dependencies").run()
+
+        //then no exceptions are thrown because we forced a certain version of conflicting dependency
+    }
+
+    @Test
+    void "can force arbitrary version of a module and avoid conflict"() {
+        repo.module("org", "foo", '1.3.3').publish()
+        repo.module("org", "foobar", '1.3.3').publish()
+        repo.module("org", "foo", '1.4.4').publish()
+        repo.module("org", "foo", '1.5.5').publish()
+
+        def settingsFile = file("settings.gradle")
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories {
+		maven { url "${repo.uri}" }
+	}
+	group = 'org.foo.unittests'
+	version = '1.0'
+}
+
+project(':api') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.4.4')
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':tool') {
+	dependencies {
+		compile project(':api')
+		compile project(':impl')
+	}
+    task checkDeps(dependsOn: configurations.compile) << {
+        assert configurations.compile*.name == ['api-1.0.jar', 'impl-1.0.jar', 'foo-1.5.5.jar']
+        def metaData = configurations.compile.resolvedConfiguration
+        def api = metaData.firstLevelModuleDependencies.find { it.moduleName == 'api' }
+        assert api.children.size() == 1
+        assert api.children.find { it.moduleName == 'foo' && it.moduleVersion == '1.5.5' }
+        def impl = metaData.firstLevelModuleDependencies.find { it.moduleName == 'impl' }
+        assert impl.children.size() == 1
+        assert impl.children.find { it.moduleName == 'foo' && it.moduleVersion == '1.5.5' }
+    }
+}
+
+allprojects {
+    configurations.all {
+        resolutionStrategy {
+            failOnVersionConflict()
+            force 'org:foo:1.5.5'
+        }
+    }
+}
+
+"""
+
+        // expect
+        executer.withTasks(":tool:checkDeps").run()
+    }
+
+    @Test
+    void "resolves to the latest version by default"() {
+        repo.module("org", "foo", '1.3.3').publish()
+        repo.module("org", "foo", '1.4.4').publish()
+
+        def settingsFile = file("settings.gradle")
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories {
+		maven { url "${repo.uri}" }
+	}
+}
+
+project(':api') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.4.4')
+	}
+}
+
+project(':tool') {
+	dependencies {
+		compile project(':api')
+		compile project(':impl')
+	}
+    task checkDeps(dependsOn: configurations.compile) << {
+        assert configurations.compile*.name == ['api.jar', 'impl.jar', 'foo-1.4.4.jar']
+    }
+}
+"""
+
+        //expect
+        executer.withTasks("tool:checkDeps").run()
+    }
+
+    @Test
+    void "latest strategy respects forced modules"() {
+        repo.module("org", "foo", '1.3.3').publish()
+        repo.module("org", "foo", '1.4.4').publish()
+
+        def settingsFile = file("settings.gradle")
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories {
+		maven { url "${repo.uri}" }
+	}
+}
+
+project(':api') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.4.4')
+	}
+}
+
+project(':tool') {
+	dependencies {
+		compile project(':api')
+		compile project(':impl')
+	}
+	configurations.all {
+	    resolutionStrategy {
+	        failOnVersionConflict()
+	        force 'org:foo:1.3.3'
+	    }
+	}
+    task checkDeps(dependsOn: configurations.compile) << {
+        assert configurations.compile*.name == ['api.jar', 'impl.jar', 'foo-1.3.3.jar']
+    }
+}
+"""
+
+        //expect
+        executer.withTasks("tool:checkDeps").run()
+    }
+
+    @Test
+    void "can force the version of a particular module"() {
+        repo.module("org", "foo", '1.3.3').publish()
+        repo.module("org", "foo", '1.4.4').publish()
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+apply plugin: 'java'
+repositories {
+    maven { url "${repo.uri}" }
+}
+
+dependencies {
+    compile 'org:foo:1.3.3'
+}
+
+configurations.all {
+    resolutionStrategy.force 'org:foo:1.4.4'
+}
+
+task checkDeps << {
+    assert configurations.compile*.name == ['foo-1.4.4.jar']
+}
+"""
+
+        //expect
+        executer.withTasks("checkDeps").run()
+    }
+
+    @Test
+    void "can force the version of a direct dependency"() {
+        repo.module("org", "foo", '1.3.3').publish()
+        repo.module("org", "foo", '1.4.4').publish()
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+apply plugin: 'java'
+repositories {
+    maven { url "${repo.uri}" }
+}
+
+dependencies {
+    compile 'org:foo:1.4.4'
+    compile ('org:foo:1.3.3') { force = true }
+}
+
+task checkDeps << {
+    assert configurations.compile*.name == ['foo-1.3.3.jar']
+}
+"""
+
+        //expect
+        executer.withTasks("checkDeps").run()
+    }
+
+    @Test
+    void "forcing transitive dependency does not add extra dependency"() {
+        repo.module("org", "foo", '1.3.3').publish()
+        repo.module("hello", "world", '1.4.4').publish()
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+apply plugin: 'java'
+repositories {
+    maven { url "${repo.uri}" }
+}
+
+dependencies {
+    compile 'org:foo:1.3.3'
+}
+
+configurations.all {
+    resolutionStrategy.force 'hello:world:1.4.4'
+}
+
+task checkDeps << {
+    assert configurations.compile*.name == ['foo-1.3.3.jar']
+}
+"""
+
+        //expect
+        executer.withTasks("checkDeps").run()
+    }
+
+    @Test
+    void "does not attempt to resolve an evicted dependency"() {
+        repo.module("org", "external", "1.2").publish()
+        repo.module("org", "dep", "2.2").dependsOn("org", "external", "1.0").publish()
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+repositories {
+    maven { url "${repo.uri}" }
+}
+
+configurations { compile }
+
+dependencies {
+    compile 'org:external:1.2'
+    compile 'org:dep:2.2'
+}
+
+task checkDeps << {
+    assert configurations.compile*.name == ['external-1.2.jar', 'dep-2.2.jar']
+}
+"""
+
+        //expect
+        executer.withTasks("checkDeps").run()
+    }
+
+    @Test
+    void "resolves dynamic dependency before resolving conflict"() {
+        repo.module("org", "external", "1.2").publish()
+        repo.module("org", "external", "1.4").publish()
+        repo.module("org", "dep", "2.2").dependsOn("org", "external", "1.+").publish()
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+repositories {
+    maven { url "${repo.uri}" }
+}
+
+configurations { compile }
+
+dependencies {
+    compile 'org:external:1.2'
+    compile 'org:dep:2.2'
+}
+
+task checkDeps << {
+    assert configurations.compile*.name == ['dep-2.2.jar', 'external-1.4.jar']
+}
+"""
+
+        //expect
+        executer.withTasks("checkDeps").run()
+    }
+
+    @Test
+    void "fails when version selected by conflict resolution does not exist"() {
+        repo.module("org", "external", "1.2").publish()
+        repo.module("org", "dep", "2.2").dependsOn("org", "external", "1.4").publish()
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+repositories {
+    maven { url "${repo.uri}" }
+}
+
+configurations { compile }
+
+dependencies {
+    compile 'org:external:1.2'
+    compile 'org:dep:2.2'
+}
+
+task checkDeps << {
+    assert configurations.compile*.name == ['external-1.2.jar', 'dep-2.2.jar']
+}
+"""
+
+        //expect
+        def failure = executer.withTasks("checkDeps").runWithFailure()
+        failure.assertHasCause("Could not find org:external:1.4.")
+    }
+
+    @Test
+    void "does not fail when evicted version does not exist"() {
+        repo.module("org", "external", "1.4").publish()
+        repo.module("org", "dep", "2.2").dependsOn("org", "external", "1.4").publish()
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+repositories {
+    maven { url "${repo.uri}" }
+}
+
+configurations { compile }
+
+dependencies {
+    compile 'org:external:1.2'
+    compile 'org:dep:2.2'
+}
+
+task checkDeps << {
+    assert configurations.compile*.name == ['dep-2.2.jar', 'external-1.4.jar']
+}
+"""
+
+        //expect
+        executer.withTasks("checkDeps").run()
+    }
+
+    @Test
+    void "takes newest dynamic version when dynamic version forced"() {
+        //given
+        repo.module("org", "foo", '1.3.0').publish()
+
+        repo.module("org", "foo", '1.4.1').publish()
+        repo.module("org", "foo", '1.4.4').publish()
+        repo.module("org", "foo", '1.4.9').publish()
+
+        repo.module("org", "foo", '1.6.0').publish()
+
+        def settingsFile = file("settings.gradle")
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories {
+		maven { url "${repo.uri}" }
+	}
+}
+
+project(':api') {
+	dependencies {
+		compile 'org:foo:1.4.4'
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile 'org:foo:1.4.1'
+	}
+}
+
+project(':tool') {
+
+	dependencies {
+		compile project(':api'), project(':impl'), 'org:foo:1.3.0'
+	}
+
+	configurations.all {
+	    resolutionStrategy {
+	        force 'org:foo:1.4+'
+	        failOnVersionConflict()
+	    }
+	}
+
+	task checkDeps << {
+        assert configurations.compile*.name.contains('foo-1.4.9.jar')
+    }
+}
+
+"""
+
+        //expect
+        executer.withTasks("tool:checkDeps").run()
+    }
+
+    @Test
+    void "parent pom does not participate in forcing mechanism"() {
+        //given
+        repo.module("org", "foo", '1.3.0').publish()
+        repo.module("org", "foo", '2.4.0').publish()
+
+        def parent = repo.module("org", "someParent", "1.0")
+        parent.type = 'pom'
+        parent.dependsOn("org", "foo", "1.3.0")
+        parent.publish()
+
+        def otherParent = repo.module("org", "someParent", "2.0")
+        otherParent.type = 'pom'
+        otherParent.dependsOn("org", "foo", "2.4.0")
+        otherParent.publish()
+
+        def module = repo.module("org", "someArtifact", '1.0')
+        module.parentPomSection = """
+<parent>
+  <groupId>org</groupId>
+  <artifactId>someParent</artifactId>
+  <version>1.0</version>
+</parent>
+"""
+        module.publish()
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+apply plugin: 'java'
+repositories {
+    maven { url "${repo.uri}" }
+}
+
+dependencies {
+    compile 'org:someArtifact:1.0'
+}
+
+configurations.all {
+    resolutionStrategy {
+        force 'org:someParent:2.0'
+        failOnVersionConflict()
+    }
+}
+
+task checkDeps << {
+    def deps = configurations.compile*.name
+    assert deps.contains('someArtifact-1.0.jar')
+    assert deps.contains('foo-1.3.0.jar')
+    assert deps.size() == 2
+}
+"""
+
+        //expect
+        executer.withTasks("checkDeps").withArguments('-s').run()
+    }
+
+    @Test
+    void "previously evicted nodes should contain correct target version"() {
+        /*
+        a1->b1
+        a2->b2->a1
+
+        resolution process:
+
+        1. stop resolution, resolve conflict a1 vs a2
+        2. select a2, restart resolution
+        3. stop, resolve b1 vs b2
+        4. select b2, restart
+        5. resolve b2 dependencies, a1 has been evicted previously but it should show correctly on the report
+           ('dependencies' report pre 1.2 would not show the a1 dependency leaf for this scenario)
+        */
+
+        ivyRepo.module("org", "b", '1.0').publish()
+        ivyRepo.module("org", "a", '1.0').dependsOn("org", "b", '1.0').publish()
+        ivyRepo.module("org", "b", '2.0').dependsOn("org", "a", "1.0").publish()
+        ivyRepo.module("org", "a", '2.0').dependsOn("org", "b", '2.0').publish()
+
+        file("build.gradle") << """
+            repositories {
+                ivy { url "${ivyRepo.uri}" }
+            }
+
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:a:1.0', 'org:a:2.0'
+            }
+            task checkDeps << {
+                assert configurations.conf*.name == ['a-2.0.jar', 'b-2.0.jar']
+                def result = configurations.conf.incoming.resolutionResult
+                assert result.allModuleVersions.size() == 3
+                def root = result.root
+                assert root.dependencies*.toString() == ['org:a:1.0 -> org:a:2.0', 'org:a:2.0']
+                def a = result.allModuleVersions.find { it.id.name == 'a' }
+                assert a.dependencies*.toString() == ['org:b:2.0']
+                def b = result.allModuleVersions.find { it.id.name == 'b' }
+                assert b.dependencies*.toString() == ['org:a:1.0 -> org:a:2.0']
+            }
+        """
+
+        executer.withTasks("checkDeps").run()
+    }
+
+    @Test
+    @Issue("GRADLE-2555")
+    void "can deal with transitive with parent in conflict"() {
+        /*
+            Graph looks like…
+
+            \--- org:a:1.0
+                 \--- org:in-conflict:1.0 -> 2.0
+                      \--- org:target:1.0
+                           \--- org:target-child:1.0
+            \--- org:b:1.0
+                 \--- org:b-child:1.0
+                      \--- org:in-conflict:2.0 (*)
+
+            This is the simplest structure I could boil it down to that produces the error.
+            - target *must* have a child
+            - Having "b" depend directly on "in-conflict" does not produce the error, needs to go through "b-child"
+         */
+
+        mavenRepo.module("org", "target-child", "1.0").
+                publish()
+
+        mavenRepo.module("org", "target", "1.0").
+                dependsOn("org", "target-child", "1.0").
+                publish()
+
+        mavenRepo.module("org", "in-conflict", "1.0").
+                dependsOn("org", "target", "1.0").
+                publish()
+
+        mavenRepo.module("org", "in-conflict", "2.0").
+                dependsOn("org", "target", "1.0").
+                publish()
+
+        mavenRepo.module("org", "a", '1.0').
+                dependsOn("org", "in-conflict", "1.0").
+                publish()
+
+        mavenRepo.module("org", "b-child", '1.0').
+                dependsOn("org", "in-conflict", "2.0").
+                publish()
+
+        mavenRepo.module("org", "b", '1.0').
+                dependsOn("org", "b-child", "1.0").
+                publish()
+
+        when:
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+
+            configurations { conf }
+
+            dependencies {
+                conf "org:a:1.0", "org:b:1.0"
+            }
+
+        task checkDeps << {
+            assert configurations.conf*.name == ['a-1.0.jar', 'b-1.0.jar', 'target-child-1.0.jar', 'target-1.0.jar', 'in-conflict-2.0.jar', 'b-child-1.0.jar']
+            def result = configurations.conf.incoming.resolutionResult
+            assert result.allModuleVersions.size() == 7
+            def a = result.allModuleVersions.find { it.id.name == 'a' }
+            assert a.dependencies*.toString() == ['org:in-conflict:1.0 -> org:in-conflict:2.0']
+            def bChild = result.allModuleVersions.find { it.id.name == 'b-child' }
+            assert bChild.dependencies*.toString() == ['org:in-conflict:2.0']
+            def target = result.allModuleVersions.find { it.id.name == 'target' }
+            assert target.dependents*.from*.toString() == ['org:in-conflict:2.0']
+        }
+        """
+
+        executer.withTasks("checkDeps").run()
+    }
+
+    //TODO SF add coverage with conflicting forced modules
+
+    def getRepo() {
+        return maven(file("repo"))
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AbstractCacheReuseCrossVersionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AbstractCacheReuseCrossVersionIntegrationTest.groovy
new file mode 100644
index 0000000..285c1f5
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AbstractCacheReuseCrossVersionIntegrationTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.integtests.resolve.artifactreuse
+
+import org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager
+import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
+import org.gradle.integtests.fixtures.executer.UnderDevelopmentGradleDistribution
+
+/**
+ * by Szczepan Faber, created at: 11/27/12
+ */
+abstract class AbstractCacheReuseCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
+
+    /**
+     * **** README ****
+     *
+     * If this test fails:
+     *  1. Make sure BasicGradleDistribution.artifactCacheVersion settings are correct
+     *  2. Think about improving this test so that we don't have to manually fix things ;)
+     */
+
+    void setup() {
+        assert DefaultCacheLockingManager.CACHE_LAYOUT_VERSION == new UnderDevelopmentGradleDistribution().artifactCacheLayoutVersion
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AliasedArtifactResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AliasedArtifactResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..10de9b2
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AliasedArtifactResolutionIntegrationTest.groovy
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.artifactreuse
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import spock.lang.Ignore
+
+class AliasedArtifactResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+    def mavenRepo1 = mavenHttpRepo("maven1")
+    def mavenRepo2 = mavenHttpRepo("maven2")
+    def ivyRepo1 = ivyHttpRepo("ivy1")
+    def ivyRepo2 = ivyHttpRepo("ivy2")
+
+    def "setup"() {
+        server.start()
+
+        buildFile << """
+            repositories {
+                if (project.hasProperty('mavenRepository1')) {
+                    maven { url '${mavenRepo1.uri}' }
+                } else if (project.hasProperty('mavenRepository2')) {
+                    maven { url '${mavenRepo2.uri}' }
+                } else if (project.hasProperty('ivyRepository1')) {
+                    ivy { url '${ivyRepo1.uri}' }
+                } else if (project.hasProperty('ivyRepository2')) {
+                    ivy { url '${ivyRepo2.uri}' }
+                } else if (project.hasProperty('fileRepository')) {
+                    maven { url '${mavenRepo.uri}' }
+                }
+            }
+            configurations { compile }
+            dependencies {
+                compile 'org.name:projectB:1.0'
+            }
+
+            task retrieve(type: Sync) {
+                into 'libs'
+                from configurations.compile
+            }
+        """
+    }
+
+    def "does not re-download maven artifact downloaded from a different maven repository when sha1 matches"() {
+        when:
+        def projectBModuleRepo1 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
+        projectBModuleRepo1.pom.expectGet()
+        projectBModuleRepo1.artifact.expectGet()
+
+        then:
+        succeedsWith 'mavenRepository1'
+
+        when:
+        def projectBModuleRepo2 = mavenRepo2.module('org.name', 'projectB', '1.0').publish()
+        def projectBArtifactRepo2 = projectBModuleRepo2.artifact
+        projectBModuleRepo2.pom.expectHead()
+        projectBModuleRepo2.pom.sha1.expectGet()
+        projectBArtifactRepo2.expectHead()
+        projectBArtifactRepo2.sha1.expectGet()
+
+        then:
+        succeedsWith 'mavenRepository2'
+    }
+
+    def "does not re-download ivy artifact downloaded from a different ivy repository when sha1 matches"() {
+        when:
+        def projectBRepo1 = ivyRepo1.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo1.expectIvyGet()
+        projectBRepo1.expectJarGet()
+
+        then:
+        succeedsWith 'ivyRepository1'
+
+        when:
+        def projectBRepo2 = ivyRepo2.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo2.expectIvyHead()
+        projectBRepo2.expectIvySha1Get()
+        projectBRepo2.expectJarHead()
+        projectBRepo2.expectJarSha1Get()
+
+        then:
+        succeedsWith 'ivyRepository2'
+    }
+
+    def "does not re-download ivy artifact downloaded from a maven repository when sha1 matches"() {
+        when:
+        def projectBRepo1 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo1.pom.expectGet()
+        projectBRepo1.getArtifact().expectGet()
+
+        then:
+        succeedsWith 'mavenRepository1'
+
+        when:
+        def projectBRepo2 = ivyRepo1.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo2.expectIvyGet()
+        projectBRepo2.expectJarHead()
+        projectBRepo2.expectJarSha1Get()
+
+        then:
+        succeedsWith 'ivyRepository1'
+    }
+
+    def "does not re-download maven artifact downloaded from a ivy repository when sha1 matches"() {
+        when:
+        def projectBRepo1 = ivyRepo1.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo1.expectIvyGet()
+        projectBRepo1.expectJarGet()
+
+        then:
+        succeedsWith 'ivyRepository1'
+
+        when:
+        def projectBRepo2 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo2.pom.expectGet()
+        def projectBRepo2Artifact = projectBRepo2.artifact
+        projectBRepo2Artifact.expectHead()
+        projectBRepo2Artifact.sha1.expectGet()
+
+        then:
+        succeedsWith 'mavenRepository1'
+    }
+
+    @Ignore("File repository does not cache artifacts locally, so they are not used to prevent download")
+    def "does not download artifact previously accessed from a file uri when sha1 matches"() {
+        given:
+        succeedsWith 'fileRepository'
+
+        when:
+        def projectBRepo2 = mavenRepo2.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo2.pom.expectHead()
+        projectBRepo2.pom.sha1.expectGet()
+        projectBRepo2.artifact.expectHead()
+        projectBRepo2.artifact.sha1.expectGet()
+
+        then:
+        succeedsWith 'mavenRepository2'
+    }
+
+    def "does re-download maven artifact downloaded from a different URI when sha1 not found"() {
+        when:
+        def projectBRepo1 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo1.pom.expectGet()
+        projectBRepo1.getArtifact().expectGet()
+
+        then:
+        succeedsWith 'mavenRepository1'
+
+        when:
+        def projectBRepo2 = mavenRepo2.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo2.pom.expectHead()
+        projectBRepo2.pom.sha1.expectGetMissing()
+        projectBRepo2.pom.expectGet()
+        projectBRepo2.artifact.expectHead()
+        projectBRepo2.artifact.sha1.expectGetMissing()
+        projectBRepo2.getArtifact().expectGet()
+
+        then:
+        succeedsWith 'mavenRepository2'
+    }
+
+    def "does re-download maven artifact downloaded from a different URI when sha1 does not match"() {
+        when:
+        def projectBRepo1 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo1.pom.expectGet()
+        projectBRepo1.getArtifact().expectGet()
+
+        then:
+        succeedsWith 'mavenRepository1'
+
+        when:
+        def projectBRepo2 = mavenRepo2.module('org.name', 'projectB', '1.0').publishWithChangedContent()
+        projectBRepo2.pom.expectHead()
+        projectBRepo2.pom.sha1.expectGet()
+        projectBRepo2.pom.expectGet()
+
+        def projRepo2BArtifact = projectBRepo2.artifact
+        projRepo2BArtifact.expectHead()
+        projectBRepo2.artifact.sha1.expectGet()
+        projRepo2BArtifact.expectGet()
+
+        then:
+        succeedsWith 'mavenRepository2'
+    }
+
+    def succeedsWith(repository) {
+        executer.withArguments('-i', "-P${repository}")
+        def result = succeeds 'retrieve'
+        file('libs').assertHasDescendants('projectB-1.0.jar')
+        server.resetExpectations()
+        return result
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/CacheReuseCrossVersionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/CacheReuseCrossVersionIntegrationTest.groovy
new file mode 100644
index 0000000..81d7596
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/CacheReuseCrossVersionIntegrationTest.groovy
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.artifactreuse
+
+import org.gradle.integtests.fixtures.TargetVersions
+import org.gradle.test.fixtures.maven.MavenFileRepository
+import org.gradle.test.fixtures.maven.MavenHttpRepository
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.junit.Rule
+
+import org.gradle.integtests.fixtures.IgnoreVersions
+import org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager
+
+ at TargetVersions('1.0-milestone-6+')
+ at IgnoreVersions({ it.artifactCacheLayoutVersion == DefaultCacheLockingManager.CACHE_LAYOUT_VERSION })
+class CacheReuseCrossVersionIntegrationTest extends AbstractCacheReuseCrossVersionIntegrationTest {
+    @Rule public final HttpServer server = new HttpServer()
+    final MavenHttpRepository httpRepo = new MavenHttpRepository(server, new MavenFileRepository(file("maven-repo")))
+
+    def "uses cached artifacts from previous Gradle version when no sha1 header"() {
+        given:
+        def projectB = httpRepo.module('org.name', 'projectB', '1.0').publish()
+        server.sendSha1Header = false
+        server.start()
+        buildFile << """
+repositories {
+    maven { url '${httpRepo.uri}' }
+}
+configurations { compile }
+dependencies {
+    compile 'org.name:projectB:1.0'
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+        and:
+        def userHome = file('user-home')
+
+        when:
+        projectB.allowAll()
+
+        and:
+        version previous withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+
+        then:
+        file('libs').assertHasDescendants('projectB-1.0.jar')
+        def snapshot = file('libs/projectB-1.0.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        projectB.pom.expectHead()
+        projectB.pom.sha1.expectGet()
+        projectB.artifact.expectHead()
+        projectB.artifact.sha1.expectGet()
+
+        and:
+        version current withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+
+        then:
+        file('libs').assertHasDescendants('projectB-1.0.jar')
+        file('libs/projectB-1.0.jar').assertContentsHaveNotChangedSince(snapshot)
+    }
+
+    def "uses cached artifacts from previous Gradle version with sha1 header"() {
+        given:
+        def projectB = httpRepo.module('org.name', 'projectB', '1.0').publish()
+        server.sendSha1Header = true
+        server.start()
+        buildFile << """
+repositories {
+    maven { url '${httpRepo.uri}' }
+}
+configurations { compile }
+dependencies {
+    compile 'org.name:projectB:1.0'
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+        and:
+        def userHome = file('user-home')
+
+        when:
+        projectB.allowAll()
+
+        and:
+        version previous withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+
+        then:
+        file('libs').assertHasDescendants('projectB-1.0.jar')
+        def snapshot = file('libs/projectB-1.0.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        projectB.pom.expectHead()
+        projectB.artifact.expectHead()
+
+        and:
+        version current withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+
+        then:
+        file('libs').assertHasDescendants('projectB-1.0.jar')
+        file('libs/projectB-1.0.jar').assertContentsHaveNotChangedSince(snapshot)
+    }
+
+    def "uses cached artifacts from previous Gradle version that match dynamic version"() {
+        given:
+        def projectB = httpRepo.module('org.name', 'projectB', '1.1').publish()
+        server.start()
+
+        buildFile << """
+repositories {
+    maven { url '${httpRepo.uri}' }
+}
+configurations { compile }
+dependencies {
+    compile 'org.name:projectB:[1.0,2.0]'
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+        and:
+        def userHome = file('user-home')
+
+        when:
+        httpRepo.expectMetaDataGet("org.name", "projectB")
+        projectB.allowAll()
+
+        and:
+        version previous withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+
+        then:
+        file('libs').assertHasDescendants('projectB-1.1.jar')
+        def snapshot = file('libs/projectB-1.1.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        httpRepo.expectMetaDataGet("org.name", "projectB")
+        projectB.pom.expectHead()
+        projectB.pom.sha1.expectGet()
+        projectB.artifact.expectHead()
+        projectB.artifact.sha1.expectGet()
+
+        and:
+        version current withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+
+        then:
+        file('libs').assertHasDescendants('projectB-1.1.jar')
+        file('libs/projectB-1.1.jar').assertContentsHaveNotChangedSince(snapshot)
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/M3CacheReuseCrossVersionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/M3CacheReuseCrossVersionIntegrationTest.groovy
new file mode 100644
index 0000000..01a9040
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/M3CacheReuseCrossVersionIntegrationTest.groovy
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.artifactreuse
+
+import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
+import org.gradle.integtests.fixtures.TargetVersions
+import org.gradle.test.fixtures.maven.MavenHttpRepository
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.junit.Rule
+
+// TODO:DAZ Support for milestone-3 does not include POM reuse. We should probably ditch milestone-3 support after 1.0.
+ at TargetVersions('1.0-milestone-3')
+class M3CacheReuseCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
+    @Rule public final HttpServer server = new HttpServer()
+    final MavenHttpRepository remoteRepo = new MavenHttpRepository(server, mavenRepo)
+
+    def "uses cached artifacts from previous Gradle version"() {
+        given:
+        def projectB = remoteRepo.module('org.name', 'projectB').publish()
+
+        server.start()
+        buildFile << """
+repositories {
+    mavenRepo(urls: ['${remoteRepo.uri}'])
+}
+configurations { compile }
+dependencies {
+    compile 'org.name:projectB:1.0'
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+        and:
+        def userHome = file('user-home')
+
+        when:
+        projectB.allowAll()
+
+        and:
+        version previous withGradleUserHomeDir userHome withTasks 'retrieve' run()
+
+        then:
+        file('libs').assertHasDescendants('projectB-1.0.jar')
+        def snapshot = file('libs/projectB-1.0.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        projectB.pom.expectGet()
+        projectB.artifact.expectHead()
+        projectB.artifact.sha1.expectGet()
+
+        and:
+        version current withGradleUserHomeDir userHome withTasks 'retrieve' run()
+
+        then:
+        file('libs').assertHasDescendants('projectB-1.0.jar')
+        file('libs/projectB-1.0.jar').assertContentsHaveNotChangedSince(snapshot)
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenM2CacheReuseIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenM2CacheReuseIntegrationTest.groovy
new file mode 100644
index 0000000..9e6662f
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenM2CacheReuseIntegrationTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.artifactreuse
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.test.fixtures.maven.M2Installation
+
+class MavenM2CacheReuseIntegrationTest extends AbstractDependencyResolutionTest {
+    def "uses cached artifacts from maven local cache"() {
+        given:
+        def module1 = mavenHttpRepo.module('gradletest.maven.local.cache.test', "foo", "1.0").publish()
+        def m2Installation = new M2Installation(testDirectory).generateGlobalSettingsFile()
+        def module2 = m2Installation.mavenRepo().module('gradletest.maven.local.cache.test', "foo", "1.0").publish()
+        server.start()
+
+        buildFile.text = """
+repositories {
+    maven { url "${mavenHttpRepo.uri}" }
+}
+configurations { compile }
+dependencies {
+    compile 'gradletest.maven.local.cache.test:foo:1.0'
+}
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'build'
+}
+"""
+        and:
+        module1.pom.expectHead()
+        module1.pom.sha1.expectGet()
+        module1.artifact.expectHead()
+        module1.artifact.sha1.expectGet()
+
+        when:
+        using m2Installation
+        run 'retrieve'
+
+        then:
+        file('build/foo-1.0.jar').assertIsCopyOf(module2.artifactFile)
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/ResolutionOverrideIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/ResolutionOverrideIntegrationTest.groovy
new file mode 100644
index 0000000..737544f
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/ResolutionOverrideIntegrationTest.groovy
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.artifactreuse
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.hamcrest.Matchers
+
+class ResolutionOverrideIntegrationTest extends AbstractDependencyResolutionTest {
+    public void "will refresh non-changing module when run with --refresh-dependencies"() {
+        given:
+        server.start()
+        def module = mavenHttpRepo.module('org.name', 'projectA', '1.2').publish()
+
+        and:
+        buildFile << """
+repositories {
+    maven { url "${mavenHttpRepo.uri}" }
+}
+configurations { compile }
+dependencies { compile 'org.name:projectA:1.2' }
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+        and:
+        module.allowAll()
+
+        when:
+        succeeds 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+        def snapshot = file('libs/projectA-1.2.jar').snapshot()
+
+        when:
+        module.publishWithChangedContent()
+
+        and:
+        server.resetExpectations()
+        module.pom.expectHead()
+        module.pom.sha1.expectGet()
+        module.pom.expectGet()
+        module.artifact.expectHead()
+        module.artifact.sha1.expectGet()
+        module.artifact.expectGet()
+
+        and:
+        executer.withArguments('--refresh-dependencies')
+        succeeds 'retrieve'
+
+        then:
+        file('libs/projectA-1.2.jar').assertIsCopyOf(module.artifactFile).assertHasChangedSince(snapshot)
+    }
+
+    public void "will recover from missing module when run with --refresh-dependencies"() {
+        server.start()
+
+        given:
+        def module = mavenHttpRepo.module('org.name', 'projectA', '1.2').publish()
+        def artifact = module.artifact
+
+        buildFile << """
+repositories {
+    maven {
+        url "${mavenHttpRepo.uri}"
+    }
+}
+configurations { missing }
+dependencies {
+    missing 'org.name:projectA:1.2'
+}
+task showMissing << { println configurations.missing.files }
+"""
+
+        when:
+        module.pom.expectGetMissing()
+        artifact.expectHeadMissing()
+
+        then:
+        fails("showMissing")
+
+        when:
+        server.resetExpectations()
+        module.pom.expectGet()
+        module.getArtifact().expectGet()
+
+        then:
+        executer.withArguments("--refresh-dependencies")
+        succeeds('showMissing')
+    }
+
+    public void "will recover from missing artifact when run with --refresh-dependencies"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    maven {
+        url "${mavenHttpRepo.uri}"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'org.name:projectA:1.2'
+}
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        and:
+        def module = mavenHttpRepo.module('org.name', 'projectA', '1.2').publish()
+        def artifact = module.artifact
+
+        when:
+        module.pom.expectGet()
+        artifact.expectGetMissing()
+
+        then:
+        fails "retrieve"
+
+        when:
+        server.resetExpectations()
+        module.pom.expectHead()
+        artifact.expectGet()
+
+        then:
+        executer.withArguments("--refresh-dependencies")
+        succeeds 'retrieve'
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+    }
+
+    public void "will not expire cache entries when run with offline flag"() {
+
+        given:
+        server.start()
+        def module = mavenHttpRepo.module("org.name", "unique", "1.0-SNAPSHOT").publish()
+
+        and:
+        buildFile << """
+repositories {
+    maven { url "${mavenHttpRepo.uri}" }
+}
+configurations { compile }
+configurations.all {
+    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+dependencies {
+    compile "org.name:unique:1.0-SNAPSHOT"
+}
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when: "Server handles requests"
+        module.allowAll()
+
+        and: "We resolve dependencies"
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar')
+        def snapshot = file('libs/unique-1.0-SNAPSHOT.jar').snapshot()
+
+        when:
+        module.publishWithChangedContent()
+
+        and: "We resolve again, offline"
+        server.resetExpectations()
+        executer.withArguments('--offline')
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar')
+        file('libs/unique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshot)
+    }
+
+    public void "does not attempt to contact server when run with offline flag"() {
+        given:
+        server.start()
+
+        and:
+        buildFile << """
+repositories {
+    maven { url "${mavenHttpRepo.uri}" }
+}
+configurations { compile }
+dependencies { compile 'org.name:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+        when:
+        executer.withArguments("--offline")
+
+        then:
+        fails 'listJars'
+
+        and:
+        failure.assertHasDescription('Execution failed for task \':listJars\'.')
+        failure.assertHasCause('Could not resolve all dependencies for configuration \':compile\'.')
+        failure.assertThatCause(Matchers.containsString('No cached version of org.name:projectA:1.2 available for offline mode'))
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/SameCacheUsageCrossVersionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/SameCacheUsageCrossVersionIntegrationTest.groovy
new file mode 100644
index 0000000..3daa366
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/SameCacheUsageCrossVersionIntegrationTest.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.integtests.resolve.artifactreuse
+
+import org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager
+import org.gradle.integtests.fixtures.IgnoreVersions
+import org.gradle.integtests.fixtures.TargetVersions
+import org.gradle.test.fixtures.maven.MavenFileRepository
+import org.gradle.test.fixtures.maven.MavenHttpRepository
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.junit.Rule
+
+ at TargetVersions('1.0-milestone-6+')
+ at IgnoreVersions({ it.artifactCacheLayoutVersion != DefaultCacheLockingManager.CACHE_LAYOUT_VERSION })
+class SameCacheUsageCrossVersionIntegrationTest extends AbstractCacheReuseCrossVersionIntegrationTest {
+    @Rule public final HttpServer server = new HttpServer()
+    final MavenHttpRepository httpRepo = new MavenHttpRepository(server, new MavenFileRepository(file("maven-repo")))
+
+    def "incurs zero remote requests when cache version not upgraded"() {
+        given:
+        def projectB = httpRepo.module('org.name', 'projectB', '1.0').publish()
+        server.sendSha1Header = false
+        server.start()
+        buildFile << """
+repositories {
+    maven { url '${httpRepo.uri}' }
+}
+configurations { compile }
+dependencies {
+    compile 'org.name:projectB:1.0'
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+        and:
+        def userHome = file('user-home')
+
+        when:
+        projectB.allowAll()
+
+        and:
+        version previous withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+
+        then:
+        file('libs').assertHasDescendants('projectB-1.0.jar')
+        def snapshot = file('libs/projectB-1.0.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        //expect no http requests
+
+        and:
+        version current withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+
+        then:
+        file('libs').assertHasDescendants('projectB-1.0.jar')
+        file('libs/projectB-1.0.jar').assertContentsHaveNotChangedSince(snapshot)
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedChangingModulesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedChangingModulesIntegrationTest.groovy
new file mode 100644
index 0000000..d82d7ec
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedChangingModulesIntegrationTest.groovy
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.resolve.caching
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+public class CachedChangingModulesIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "can cache and refresh unique versioned maven artifacts with a classifier"() {
+        given:
+        server.start()
+        def repo = mavenHttpRepo("repo")
+        def module = repo.module("group", "projectA", "1.0-SNAPSHOT")
+        def sourceArtifact = module.artifact(classifier: "source")
+
+        module.publish()
+        buildFile << """
+        repositories {
+            maven {
+                name 'repo'
+                url '${repo.uri}'
+            }
+        }
+        configurations {
+            compile
+        }
+
+        configurations.all {
+            resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+        }
+
+        dependencies {
+            compile 'group:projectA:1.0-SNAPSHOT:source'
+        }
+
+        task retrieve(type: Sync) {
+            into 'libs'
+            from configurations.compile
+        }
+        """
+
+        when:
+        module.pom.expectGet()
+        sourceArtifact.expectGet()
+        module.metaData.expectGet()
+
+        then:
+        run 'retrieve'
+
+        when:
+        server.resetExpectations()
+        module.metaData.expectGet()
+        sourceArtifact.expectHead()
+        module.pom.expectHead()
+        then:
+        run 'retrieve'
+
+        when:
+        module.publishWithChangedContent()
+        server.resetExpectations()
+
+        module.metaData.expectGet()
+        module.pom.sha1.expectGet()
+        module.pom.expectHead()
+        module.pom.expectGet()
+        sourceArtifact.expectHead()
+        sourceArtifact.expectGet()
+        sourceArtifact.sha1.expectGet()
+        then:
+        run 'retrieve'
+
+        when:
+        module.publishWithChangedContent()
+        server.resetExpectations()
+        then:
+        executer.withArgument("--offline")
+        run 'retrieve'
+    }
+
+    def "can cache and refresh non unique versioned maven artifacts with a classifier"() {
+        given:
+        server.start()
+        def repo = mavenHttpRepo("repo")
+        def module = repo.module("group", "projectA", "1.0-SNAPSHOT").withNonUniqueSnapshots()
+        def sourceArtifact = module.artifact(classifier: "source")
+
+        module.publish()
+        buildFile << """
+        repositories {
+            maven {
+                name 'repo'
+                url '${repo.uri}'
+            }
+        }
+        configurations {
+            compile
+        }
+
+        configurations.all {
+            resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+        }
+
+        dependencies {
+            compile 'group:projectA:1.0-SNAPSHOT:source'
+        }
+
+        task retrieve(type: Sync) {
+            into 'libs'
+            from configurations.compile
+        }
+        """
+
+        when:
+        module.pom.expectGet()
+        sourceArtifact.expectGet()
+        module.metaData.expectGetMissing()
+
+        then:
+        run 'retrieve'
+
+        when:
+        server.resetExpectations()
+        module.metaData.expectGetMissing()
+        sourceArtifact.expectHead()
+        module.pom.expectHead()
+        then:
+        run 'retrieve'
+
+        when:
+        module.publishWithChangedContent()
+        server.resetExpectations()
+
+        module.metaData.expectGetMissing()
+        module.pom.sha1.expectGet()
+        module.pom.expectHead()
+        module.pom.expectGet()
+        sourceArtifact.expectHead()
+        sourceArtifact.expectGet()
+        sourceArtifact.sha1.expectGet()
+        then:
+        run 'retrieve'
+
+        when:
+        module.publishWithChangedContent()
+        server.resetExpectations()
+        then:
+        executer.withArgument("--offline")
+        run 'retrieve'
+    }
+
+    def "can cache and refresh ivy changing artifacts with a classifier"() {
+        given:
+        server.start()
+        def repo = ivyHttpRepo("repo")
+        def module = repo.module("group", "projectA", "1.0")
+        module.artifact(classifier: "source")
+
+        module.publish()
+        buildFile << """
+          repositories {
+              ivy {
+                  name 'repo'
+                  url '${repo.uri}'
+              }
+          }
+          configurations {
+              compile
+          }
+
+          configurations.all {
+              resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+          }
+
+          dependencies {
+                compile group: "group", name: "projectA", version: "1.0", classifier: "source", changing: true
+          }
+
+          task retrieve(type: Sync) {
+              into 'libs'
+              from configurations.compile
+          }
+          """
+        when:
+        module.expectIvyGet()
+        module.expectArtifactGet(name: "projectA", classifier: "source")
+
+        then:
+        run 'retrieve'
+
+        when:
+        server.resetExpectations()
+        module.expectIvyHead()
+        module.expectArtifactHead(name: "projectA", classifier: 'source')
+        then:
+        run 'retrieve'
+
+        when:
+        module.publishWithChangedContent()
+        server.resetExpectations()
+        module.expectIvyHead()
+        module.expectArtifactHead(name: "projectA", classifier: 'source')
+
+        module.expectIvySha1Get()
+        module.expectIvyGet()
+        module.expectArtifactGet(name: "projectA", classifier: 'source')
+        module.expectArtifactSha1Get(name: "projectA", classifier: 'source')
+
+        then:
+        run 'retrieve'
+
+        when:
+        module.publishWithChangedContent()
+        server.resetExpectations()
+        then:
+        executer.withArgument("--offline")
+        run 'retrieve'
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedDependencyResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedDependencyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..693a917
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedDependencyResolutionIntegrationTest.groovy
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.resolve.caching
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.ivy.IvyHttpModule
+import org.gradle.test.fixtures.server.http.HttpServer
+
+/**
+ * We are using Ivy here, but the strategy is the same for any kind of repository.
+ */
+class CachedDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+    IvyHttpModule module
+
+    TestFile downloaded
+    TestFile.Snapshot lastState
+
+    def setup() {
+        server.start()
+        buildFile << """
+repositories {
+    ivy { url "${ivyHttpRepo.uri}" }
+}
+
+configurations { compile }
+
+configurations.all {
+    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.1", changing: true
+}
+
+task retrieve(type: Sync) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        module = ivyHttpRepo.module("group", "projectA", "1.1").publish()
+
+        downloaded = file('build/projectA-1.1.jar')
+    }
+
+    void initialResolve() {
+        module.expectIvyGet()
+        module.expectJarGet()
+
+        resolve()
+    }
+
+    void resolve() {
+        if (downloaded.exists()) {
+            lastState = downloaded.snapshot()
+        }
+
+        succeeds ":retrieve"
+    }
+
+    void headOnlyRequests() {
+        module.expectIvyHead()
+        module.expectJarHead()
+    }
+
+    void headSha1ThenGetRequests() {
+        module.expectIvyHead()
+        module.expectIvySha1Get()
+        module.expectIvyGet()
+
+        module.expectJarHead()
+        module.expectJarSha1Get()
+        module.expectJarGet()
+    }
+
+    void sha1OnlyRequests() {
+        module.expectIvySha1Get()
+        module.expectJarSha1Get()
+    }
+
+    void sha1ThenGetRequests() {
+        module.expectIvySha1Get()
+        module.expectIvyGet()
+
+        module.expectJarSha1Get()
+        module.expectJarGet()
+    }
+
+    void headThenSha1Requests() {
+        module.expectIvyHead()
+        module.expectIvySha1Get()
+
+        module.expectJarHead()
+        module.expectJarSha1Get()
+    }
+
+    void headThenGetRequests() {
+        module.expectIvyHead()
+        module.expectIvyGet()
+
+        module.expectJarHead()
+        module.expectJarGet()
+    }
+
+    void unchangedResolve() {
+        resolve()
+        downloaded.assertHasNotChangedSince(lastState)
+    }
+
+    void changedResolve() {
+        resolve()
+        downloaded.assertHasChangedSince(lastState)
+    }
+
+    void change() {
+        module.publishWithChangedContent()
+    }
+
+    def "etags are used to determine changed"() {
+        given:
+        server.etags = HttpServer.EtagStrategy.RAW_SHA1_HEX
+        server.sendLastModified = false
+        initialResolve()
+
+        expect:
+        headOnlyRequests()
+        unchangedResolve()
+
+        when:
+        change()
+
+        then:
+        headSha1ThenGetRequests()
+        changedResolve()
+    }
+
+    def "last modified and content length are used to determine changed"() {
+        given:
+        server.etags = null
+        initialResolve()
+
+        expect:
+        headOnlyRequests()
+        unchangedResolve()
+
+        when:
+        change()
+
+        then:
+        headSha1ThenGetRequests()
+        changedResolve()
+    }
+
+    def "checksum is used when last modified and content length can't be used"() {
+        given:
+        server.etags = null
+        server.sendLastModified = false
+        initialResolve()
+
+        expect:
+        headThenSha1Requests()
+        unchangedResolve()
+
+        when:
+        change()
+
+        then:
+        headSha1ThenGetRequests()
+        changedResolve()
+    }
+
+    def "no need for sha1 request if we get it in the metadata"() {
+        given:
+        server.sendSha1Header = true
+        initialResolve()
+
+        expect:
+        headOnlyRequests()
+        unchangedResolve()
+
+        when:
+        change()
+
+        then:
+        headThenGetRequests()
+        changedResolve()
+    }
+
+    def "no need for sha1 request if we know the etag is sha1"() {
+        given:
+        server.etags = HttpServer.EtagStrategy.NEXUS_ENCODED_SHA1
+        initialResolve()
+
+        expect:
+        headOnlyRequests()
+        unchangedResolve()
+
+        when:
+        change()
+
+        then:
+        headThenGetRequests()
+        changedResolve()
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedMissingModulesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedMissingModulesIntegrationTest.groovy
new file mode 100644
index 0000000..7cb709c
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedMissingModulesIntegrationTest.groovy
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.resolve.caching
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
+import spock.lang.IgnoreIf
+
+class CachedMissingModulesIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "cached not-found information for dynamic version is ignored if module is not available in any repo"() {
+        given:
+        server.start()
+        def repo1 = mavenHttpRepo("repo1")
+        repo1.module("group", "projectA", "1.0")
+        def repo2 = mavenHttpRepo("repo2")
+        def repo2Module = repo2.module("group", "projectA", "1.0")
+
+        buildFile << """
+            repositories {
+                maven {
+                    name 'repo1'
+                    url '${repo1.uri}'
+                }
+                maven {
+                    name 'repo2'
+                    url '${repo2.uri}'
+                }
+            }
+            configurations { compile }
+            dependencies {
+                compile 'group:projectA:latest.integration'
+            }
+
+            task retrieve(type: Sync) {
+                into 'libs'
+                from configurations.compile
+            }
+            """
+
+        when:
+        repo1.expectMetaDataGetMissing("group", "projectA")
+        repo1.expectMetaDataGetMissing("group", "projectA")
+        repo1.expectDirectoryListGet("group", "projectA")
+        repo1.expectDirectoryListGet("group", "projectA")
+        repo2.expectMetaDataGetMissing("group", "projectA")
+        repo2.expectMetaDataGetMissing("group", "projectA")
+        repo2.expectDirectoryListGet("group", "projectA")
+        repo2.expectDirectoryListGet("group", "projectA")
+
+        then:
+        runAndFail 'retrieve'
+
+        when:
+        server.resetExpectations()
+        repo1.expectMetaDataGetMissing("group", "projectA")
+        repo1.expectMetaDataGetMissing("group", "projectA")
+        repo1.expectDirectoryListGet("group", "projectA")
+        repo1.expectDirectoryListGet("group", "projectA")
+        repo2Module.publish()
+        repo2.expectMetaDataGet("group", "projectA")
+        repo2Module.pom.expectGet()
+        repo2Module.getArtifact().expectGet()
+
+        then:
+        run 'retrieve'
+
+        when:
+        server.resetExpectations()
+
+        then:
+        run 'retrieve'
+    }
+
+    def "cached not-found information for fixed version is ignored if module is not available in any repo"() {
+        given:
+        server.start()
+        def repo1 = mavenHttpRepo("repo1")
+        def repo1Module = repo1.module("group", "projectA", "1.0")
+        def repo1Artifact = repo1Module.artifact
+
+        def repo2 = mavenHttpRepo("repo2")
+        def repo2Module = repo2.module("group", "projectA", "1.0")
+        def repo2Artifact = repo2Module.artifact
+
+        buildFile << """
+    repositories {
+        maven {
+            name 'repo1'
+            url '${repo1.uri}'
+        }
+        maven {
+            name 'repo2'
+            url '${repo2.uri}'
+        }
+    }
+    configurations { compile }
+    dependencies {
+        compile 'group:projectA:1.0'
+    }
+
+    task retrieve(type: Sync) {
+        into 'libs'
+        from configurations.compile
+    }
+    """
+
+        when:
+        repo1Module.pom.expectGetMissing()
+        repo1Artifact.expectHeadMissing()
+        repo2Module.pom.expectGetMissing()
+        repo2Artifact.expectHeadMissing()
+
+        then:
+        runAndFail 'retrieve'
+
+        when:
+        server.resetExpectations()
+        repo1Module.pom.expectGetMissing()
+        repo1Artifact.expectHeadMissing()
+        repo2Module.publish()
+        repo2Module.pom.expectGet()
+        repo2Artifact.expectGet()
+
+        then:
+        run 'retrieve'
+
+        when:
+        server.resetExpectations()
+
+        then:
+        run 'retrieve'
+    }
+
+    @IgnoreIf({ GradleContextualExecuter.isParallel() })
+    def "hit each remote repo only once per build and missing module"() {
+        given:
+        server.start()
+        def repo1 = mavenHttpRepo("repo1")
+        def repo1Module = repo1.module("group", "projectA", "1.0")
+        def repo1Artifact = repo1Module.artifact
+        def repo2 = mavenHttpRepo("repo2")
+        def repo2Module = repo2.module("group", "projectA", "1.0")
+        def repo2Artifact = repo2Module.artifact
+
+        settingsFile << "include 'subproject'"
+        buildFile << """
+            allprojects{
+                repositories {
+                    maven {
+                        name 'repo1'
+                        url '${repo1.uri}'
+                    }
+                    maven {
+                        name 'repo2'
+                        url '${repo2.uri}'
+                    }
+                }
+            }
+            configurations {
+                config1
+            }
+            dependencies {
+                config1 'group:projectA:1.0'
+            }
+
+            task resolveConfig1 << {
+                   configurations.config1.incoming.resolutionResult.allDependencies{
+                        it instanceof UnresolvedDependencyResult
+                   }
+            }
+
+            project(":subproject"){
+                configurations{
+                    config2
+                }
+                dependencies{
+                    config2 'group:projectA:1.0'
+                }
+                task resolveConfig2 << {
+                    configurations.config2.incoming.resolutionResult.allDependencies{
+                        it instanceof UnresolvedDependencyResult
+                    }
+                }
+            }
+        """
+        when:
+        repo1Module.pom.expectGetMissing()
+        repo1Artifact.expectHeadMissing()
+        repo2Module.pom.expectGetMissing()
+        repo2Artifact.expectHeadMissing()
+
+        then:
+        run('resolveConfig1')
+
+        when:
+        server.resetExpectations()
+        repo1Module.pom.expectGetMissing()
+        repo1Artifact.expectHeadMissing()
+        repo2Module.pom.expectGetMissing()
+        repo2Artifact.expectHeadMissing()
+
+        then:
+        run "resolveConfig1", "resolveConfig2"
+    }
+
+    def "does not hit remote repositories if version is available in local repo"() {
+        given:
+        server.start()
+        def repo1 = mavenHttpRepo("repo1")
+        def repo1Module = repo1.module("group", "projectA", "1.0")
+        def repo2 = mavenRepo("repo2")
+        def repo2Module = repo2.module("group", "projectA", "1.0")
+
+        buildFile << """
+        repositories {
+           maven {
+               name 'repo1'
+               url '${repo1.uri}'
+           }
+           maven {
+               name 'repo2'
+               url '${repo2.uri}'
+           }
+       }
+       configurations { compile }
+       dependencies {
+           compile 'group:projectA:1.0'
+       }
+
+       task retrieve(type: Sync) {
+           into 'libs'
+           from configurations.compile
+       }
+       """
+
+        when:
+        repo2Module.publish()
+        repo1Module.pom.expectGetMissing()
+        repo1Module.artifact.expectHeadMissing()
+
+        then:
+        run 'retrieve'
+
+        when:
+        server.resetExpectations()
+        then:
+        run 'retrieve'
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/RecoverFromBrokenResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/RecoverFromBrokenResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..2c70307
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/caching/RecoverFromBrokenResolutionIntegrationTest.groovy
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.resolve.caching
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.test.fixtures.maven.MavenHttpModule
+import org.hamcrest.Matchers
+
+class RecoverFromBrokenResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def repo
+    def module
+
+    def setup() {
+        server.start()
+    }
+
+    private void buildFileWithSnapshotDependency() {
+        buildFile << """
+            configurations {
+                compile
+            }
+
+            configurations.all {
+                resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+            }
+
+            dependencies {
+                compile 'group:projectA:1.0-SNAPSHOT'
+            }
+
+            task retrieve(type: Sync) {
+                into 'libs'
+                from configurations.compile
+            }
+            """
+    }
+
+    def "can run offline mode after hitting broken repo url"() {
+        given:
+        buildFileWithSnapshotDependency()
+        and:
+        noAuthorizationRepo()
+
+        publishedMavenModule()
+        when:
+        moduleAvailableViaHttp()
+        then:
+        run 'retrieve'
+
+        when:
+        server.resetExpectations()
+        server.addBroken("/")
+        then:
+        fails 'retrieve'
+
+        and:
+        //TODO should expose the failed task in the error message like
+        //failure.assertHasDescription('Execution failed for task \':retrieve\'.')
+        //failure.assertHasCause('Could not resolve all dependencies for configuration \':compile\'.')
+        failure.assertHasDescription('Could not resolve all dependencies for configuration \':compile\'.')
+        failure.assertThatCause(Matchers.containsString("Received status code 500 from server: broken"))
+
+
+        when:
+        server.resetExpectations()
+        then:
+        executer.withArgument("--offline")
+        run 'retrieve'
+        and:
+        file('libs/projectA-1.0-SNAPSHOT.jar').assertIsCopyOf(module.artifact.file)
+    }
+
+    def "can run offline mode after connection problem with repo url using unique snapshot version"() {
+        given:
+        buildFileWithSnapshotDependency()
+        noAuthorizationRepo()
+        publishedMavenModule()
+
+        when:
+        moduleAvailableViaHttp()
+
+        then:
+        run 'retrieve'
+
+        when:
+        server.resetExpectations()
+        int port = server.port
+        server.stop()
+        then:
+        fails 'retrieve'
+
+        and:
+        //TODO should expose the failed task in the error message like
+        //failure.assertHasDescription('Execution failed for task \':retrieve\'.')
+        //failure.assertHasCause('Could not resolve all dependencies for configuration \':compile\'.')
+        failure.assertHasDescription('Could not resolve all dependencies for configuration \':compile\'.')
+        failure.assertThatCause(Matchers.containsString("Connection to http://localhost:${port} refused"))
+
+        when:
+        server.resetExpectations()
+        then:
+        executer.withArgument("--offline")
+        run 'retrieve'
+        and:
+        file('libs/projectA-1.0-SNAPSHOT.jar').assertIsCopyOf(module.artifact.file)
+    }
+
+    def "can run offline mode after connection problem with repo url using non unique snapshot version"() {
+        given:
+        buildFileWithSnapshotDependency()
+        noAuthorizationRepo()
+        publishedMavenModule(true)
+
+        when:
+        moduleAvailableViaHttpWithoutMetaData()
+
+        then:
+        run 'retrieve'
+
+        when:
+        server.resetExpectations()
+        int port = server.port
+        server.stop()
+        then:
+        fails 'retrieve'
+
+        and:
+        //TODO should expose the failed task in the error message like
+        //failure.assertHasDescription('Execution failed for task \':retrieve\'.')
+        //failure.assertHasCause('Could not resolve all dependencies for configuration \':compile\'.')
+        failure.assertHasDescription('Could not resolve all dependencies for configuration \':compile\'.')
+        failure.assertThatCause(Matchers.containsString("Connection to http://localhost:${port} refused"))
+
+        when:
+        server.resetExpectations()
+        then:
+        executer.withArgument("--offline")
+        run 'retrieve'
+        and:
+        file('libs/projectA-1.0-SNAPSHOT.jar').assertIsCopyOf(module.artifact.file)
+    }
+
+    def "can run offline mode after authentication fails on remote repo"() {
+        given:
+        buildFileWithSnapshotDependency()
+        and:
+        authorizationRepo()
+        when:
+        publishedMavenModule()
+        server.resetExpectations()
+        server.expectGet('/repo/group/projectA/1.0-SNAPSHOT/maven-metadata.xml', 'username', 'password', module.metaDataFile)
+        server.expectGet("/repo/group/projectA/1.0-SNAPSHOT/${module.pomFile.name}", 'username', 'password', module.pomFile)
+        server.expectGet("/repo/group/projectA/1.0-SNAPSHOT/${module.artifactFile.name}", 'username', 'password', module.artifactFile)
+
+        then:
+        run 'retrieve'
+
+        when:
+        server.resetExpectations()
+        server.allowGetOrHead('/repo/group/projectA/1.0-SNAPSHOT/maven-metadata.xml', 'bad_username', 'password', module.metaDataFile)
+
+        then:
+        fails 'retrieve'
+
+        and:
+        //TODO should expose the failed task in the error message like
+        //failure.assertHasDescription('Execution failed for task \':retrieve\'.')
+        //failure.assertHasCause('Could not resolve all dependencies for configuration \':compile\'.')
+        failure.assertHasDescription('Could not resolve all dependencies for configuration \':compile\'.')
+        failure.assertThatCause(Matchers.containsString("Received status code 401 from server: Unauthorized"))
+
+        when:
+        server.resetExpectations()
+        then:
+        executer.withArgument("--offline")
+        run 'retrieve'
+        and:
+        file('libs/projectA-1.0-SNAPSHOT.jar').assertIsCopyOf(module.artifact.file)
+    }
+
+    def "can run offline mode after connection problem with repo when using ivy changing modules"() {
+        given:
+        def ivyRepo = ivyHttpRepo("ivyRepo")
+        def ivyModule = ivyRepo.module("group", "projectA", "1.0")
+        ivyModule.publish()
+        and:
+        buildFile.text = """
+                  repositories {
+                       ivy {
+                           name 'repo'
+                           url '${ivyRepo.uri}'
+                       }
+                  }
+                  configurations {
+                      compile
+                  }
+
+                  configurations.all {
+                      resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+                  }
+
+                  dependencies {
+                      compile group:'group', name:'projectA', version:'1.0', changing:true
+                  }
+
+                  task retrieve(type: Sync) {
+                      into 'libs'
+                      from configurations.compile
+                  }"""
+        when:
+        ivyModule.expectIvyGet()
+        ivyModule.expectJarGet()
+
+        then:
+        run 'retrieve'
+
+        when:
+        server.resetExpectations()
+        int port = server.port
+        server.stop()
+        then:
+        fails 'retrieve'
+
+        and:
+        //TODO should expose the failed task in the error message like
+        //failure.assertHasDescription('Execution failed for task \':retrieve\'.')
+        //failure.assertHasCause('Could not resolve all dependencies for configuration \':compile\'.')
+        failure.assertHasDescription('Could not resolve all dependencies for configuration \':compile\'.')
+        failure.assertThatCause(Matchers.containsString("Connection to http://localhost:${port} refused"))
+
+        when:
+        server.resetExpectations()
+        then:
+        executer.withArgument("--offline")
+        run 'retrieve'
+        and:
+        file('libs/projectA-1.0.jar').assertIsCopyOf(ivyModule.jarFile)
+    }
+
+    def moduleAvailableViaHttp() {
+        module.metaData.expectGet()
+        module.pom.expectGet()
+        module.getArtifact().expectGet()
+    }
+
+    def moduleAvailableViaHttpWithoutMetaData() {
+        module.metaData.expectGetMissing()
+        module.pom.expectGet()
+        module.getArtifact().expectGet()
+    }
+
+
+    private MavenHttpModule publishedMavenModule(withNonUniqueVersion = false) {
+        module = repo.module("group", "projectA", "1.0-SNAPSHOT")
+        if (withNonUniqueVersion) {
+            module.withNonUniqueSnapshots()
+        }
+        module.publish()
+        module
+    }
+
+    private noAuthorizationRepo() {
+        repo = mavenHttpRepo("repo")
+
+        buildFile << """
+        repositories {
+            maven {
+                name 'repo'
+                url '${repo.uri}'
+            }
+        } """
+    }
+
+    private authorizationRepo() {
+        repo = mavenHttpRepo("repo")
+        buildFile << """
+        repositories {
+            maven {
+                url '${repo.uri}'
+                credentials {
+                    password 'password'
+                    username 'username'
+                }
+            }
+        }
+        """
+    }
+
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/FileSystemResolverIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/FileSystemResolverIntegrationTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/FileSystemResolverIntegrationTest.groovy
rename to subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/FileSystemResolverIntegrationTest.groovy
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvySFtpResolverIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvySFtpResolverIntegrationTest.groovy
new file mode 100644
index 0000000..16ac149
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvySFtpResolverIntegrationTest.groovy
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.custom
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
+import org.gradle.test.fixtures.ivy.IvyRepository
+import org.gradle.test.fixtures.server.sftp.SFTPServer
+import org.junit.Rule
+
+class IvySFtpResolverIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule
+    public final SFTPServer server = new SFTPServer(this)
+
+    @Rule ProgressLoggingFixture progressLogging
+
+    def "setup"() {
+        requireOwnGradleUserHomeDir()
+    }
+
+    public void "can resolve and cache dependencies from an SFTP Ivy repository"() {
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish();
+
+        and:
+        buildFile << """
+repositories {
+    add(new org.apache.ivy.plugins.resolver.SFTPResolver()) {
+        name = "sftprepo"
+        host = "${server.hostAddress}"
+        port = ${server.port}
+        user = "simple"
+        userPassword = "simple"
+        addIvyPattern "repos/libs/[organization]/[module]/[revision]/ivy-[revision].xml"
+        addArtifactPattern "repos/libs/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
+    }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+        when:
+        succeeds 'listJars'
+
+        then:
+        server.fileRequests == ["repos/libs/group/projectA/1.2/ivy-1.2.xml",
+                "repos/libs/group/projectA/1.2/projectA-1.2.jar"
+        ] as Set
+
+        progressLogging.downloadProgressLogged("repos/libs/group/projectA/1.2/ivy-1.2.xml")
+        progressLogging.downloadProgressLogged("repos/libs/group/projectA/1.2/projectA-1.2.jar")
+
+        when:
+        server.clearRequests()
+        succeeds 'listJars'
+
+        then:
+        server.fileRequests.empty
+    }
+
+    IvyRepository ivyRepo() {
+        return ivy(server.file("repos/libs/"))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvyUrlResolverIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvyUrlResolverIntegrationTest.groovy
new file mode 100644
index 0000000..cf784ff
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvyUrlResolverIntegrationTest.groovy
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.custom
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
+import org.junit.Rule
+
+class IvyUrlResolverIntegrationTest extends AbstractDependencyResolutionTest {
+
+    @Rule ProgressLoggingFixture progressLogging
+
+    def setup() {
+        server.expectUserAgent(null) // custom resolver uses apache/ivy as useragent strings
+    }
+
+    public void "can resolve and cache dependencies from an HTTP Ivy repository"() {
+        server.start()
+        given:
+        def module = ivyHttpRepo.module('group', 'projectA', '1.2').publish()
+
+        and:
+        buildFile << """
+repositories {
+    add(new org.apache.ivy.plugins.resolver.URLResolver()) {
+        name = "repo"
+        addIvyPattern("${ivyHttpRepo.ivyPattern}")
+        addArtifactPattern("${ivyHttpRepo.artifactPattern}")
+    }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+        when:
+        module.expectIvyHead()
+        module.expectIvyGet()
+        module.expectJarHead()
+        module.expectJarGet()
+
+        then:
+        succeeds 'listJars'
+
+        and:
+        progressLogging.downloadProgressLogged(module.ivyFileUri)
+        progressLogging.downloadProgressLogged(module.jarFileUri)
+
+        when:
+        server.resetExpectations()
+
+        // No extra calls for cached dependencies
+        then:
+        succeeds 'listJars'
+    }
+
+    public void "honours changing patterns from custom resolver"() {
+        server.start()
+        given:
+        def module = ivyHttpRepo.module('group', 'projectA', '1.2-SNAPSHOT').publish()
+
+        and:
+        buildFile << """
+repositories {
+    add(new org.apache.ivy.plugins.resolver.URLResolver()) {
+        name = "repo"
+        addIvyPattern("${ivyHttpRepo.ivyPattern}")
+        addArtifactPattern("${ivyHttpRepo.artifactPattern}")
+        changingMatcher = 'regexp'
+        changingPattern = '.*SNAPSHOT.*'
+    }
+}
+configurations { compile }
+configurations.compile.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+dependencies { compile 'group:projectA:1.2-SNAPSHOT' }
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+        when:
+        module.expectIvyHead()
+        module.expectIvyGet()
+        module.expectJarHead()
+        module.expectJarGet()
+
+        run 'retrieve'
+
+        then:
+        def jarFile = file('libs/projectA-1.2-SNAPSHOT.jar')
+        jarFile.assertIsCopyOf(module.jarFile)
+        def snapshot = jarFile.snapshot()
+
+        when:
+        module.publishWithChangedContent()
+
+        server.resetExpectations()
+        // Server will be hit to get updated versions
+        module.expectIvyHead()
+        module.expectIvyGet()
+        module.expectJarHead()
+        module.expectJarGet()
+
+        run 'retrieve'
+
+        then:
+        def changedJarFile = file('libs/projectA-1.2-SNAPSHOT.jar')
+        changedJarFile.assertHasChangedSince(snapshot)
+        changedJarFile.assertIsCopyOf(module.jarFile)
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest.groovy
new file mode 100644
index 0000000..3458408
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest.groovy
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.resolve.http
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.Rule
+
+import static org.gradle.util.Matchers.containsText
+
+abstract class AbstractHttpsRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    @Rule TestResources resources
+    File clientStore // contains the client's public and private keys
+    File serverStore // contains the server's public and private keys
+
+    abstract protected String setupRepo()
+
+    def "resolve with server certificate"() {
+        setupCertStores()
+        server.enableSsl(serverStore.path, "asdfgh")
+        server.start()
+
+        def repoType = setupRepo()
+        setupBuildFile(repoType)
+
+        when:
+        executer.withArgument("-Djavax.net.ssl.trustStore=$serverStore.path")
+                .withArgument("-Djavax.net.ssl.trustStorePassword=asdfgh")
+                .withTasks('libs').run()
+
+        then:
+        file('libs').assertHasDescendants('my-module-1.0.jar')
+    }
+
+    def "resolve with server and client certificate"() {
+        setupCertStores()
+        server.enableSsl(serverStore.path, "asdfgh", clientStore.path, "asdfgh")
+        server.start()
+
+        def repoType = setupRepo()
+        setupBuildFile(repoType)
+
+        when:
+        executer.withArgument("-Djavax.net.ssl.trustStore=$serverStore.path")
+                .withArgument("-Djavax.net.ssl.trustStorePassword=asdfgh")
+                .withArgument("-Djavax.net.ssl.keyStore=$clientStore.path")
+                .withArgument("-Djavax.net.ssl.keyStorePassword=asdfgh")
+                .withTasks('libs').run()
+
+        then:
+        file('libs').assertHasDescendants('my-module-1.0.jar')
+    }
+
+    def "decent error message when client can't authenticate server"() {
+        setupCertStores()
+        server.enableSsl(serverStore.path, "asdfgh")
+        server.start()
+
+        def repoType = setupRepo()
+        setupBuildFile(repoType)
+
+        when:
+        def failure = executer.withStackTraceChecksDisabled() // Jetty logs stuff to console
+                .withArgument("-Djavax.net.ssl.trustStore=$clientStore.path") // intentionally use wrong trust store for client
+                .withArgument("-Djavax.net.ssl.trustStorePassword=asdfgh")
+                .withTasks('libs').runWithFailure()
+
+        then:
+        failure.assertThatCause(containsText("Could not GET 'https://localhost:(\\d*)/repo1/my-group/my-module/1.0/"))
+        failure.assertHasCause("peer not authenticated")
+    }
+
+    def "decent error message when server can't authenticate client"() {
+        setupCertStores()
+        server.enableSsl(serverStore.path, "asdfgh", serverStore.path, "asdfgh") // intentionally use wrong trust store for server
+        server.start()
+
+        def repoType = setupRepo()
+        setupBuildFile(repoType)
+
+        when:
+        def failure = executer.withStackTraceChecksDisabled() // Jetty logs stuff to console
+                .withArgument("-Djavax.net.ssl.trustStore=$serverStore.path")
+                .withArgument("-Djavax.net.ssl.trustStorePassword=asdfgh")
+                .withArgument("-Djavax.net.ssl.keyStore=$clientStore.path")
+                .withArgument("-Djavax.net.ssl.keyStorePassword=asdfgh")
+                .withTasks('libs').runWithFailure()
+
+        then:
+        failure.assertThatCause(containsText("Could not GET 'https://localhost:(\\d*)/repo1/my-group/my-module/1.0/"))
+        failure.assertHasCause("peer not authenticated")
+    }
+
+    def setupCertStores() {
+        clientStore = resources.dir.file("clientStore")
+        serverStore = resources.dir.file("serverStore")
+    }
+
+    private void setupBuildFile(String repoType) {
+        buildFile << """
+repositories {
+    $repoType { url 'https://localhost:${server.sslPort}/repo1' }
+}
+configurations { compile }
+dependencies {
+    compile 'my-group:my-module:1.0'
+}
+task libs(type: Copy) {
+    into 'libs'
+    from configurations.compile
+}
+        """
+    }
+}
+
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpAuthenticationDependencyResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpAuthenticationDependencyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..4f780ca
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpAuthenticationDependencyResolutionIntegrationTest.groovy
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.http
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.hamcrest.Matchers
+import spock.lang.Unroll
+
+class HttpAuthenticationDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+    static String badCredentials = "credentials{username 'testuser'; password 'bad'}"
+
+    @Unroll
+    public void "can resolve dependencies from #authScheme authenticated HTTP ivy repository"() {
+        server.start()
+        given:
+        def moduleA = ivyRepo().module('group', 'projectA', '1.2').publish()
+        ivyRepo().module('group', 'projectB', '2.1').publish()
+        ivyRepo().module('group', 'projectB', '2.2').publish()
+        def moduleB = ivyRepo().module('group', 'projectB', '2.3').publish()
+        ivyRepo().module('group', 'projectB', '3.0').publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}/repo"
+
+        credentials {
+            password 'password'
+            username 'username'
+        }
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+    compile 'group:projectB:2.+'
+}
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar','projectB-2.3.jar']
+}
+"""
+
+        when:
+        server.authenticationScheme = authScheme
+
+        and:
+        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', 'username', 'password', moduleA.ivyFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', 'username', 'password', moduleA.jarFile)
+        server.expectGetDirectoryListing("/repo/group/projectB/", 'username', 'password', moduleB.moduleDir.parentFile)
+        server.expectGet("/repo/group/projectB/2.3/ivy-2.3.xml", 'username', 'password', moduleB.ivyFile)
+        server.expectGet("/repo/group/projectB/2.3/projectB-2.3.jar", 'username', 'password', moduleB.jarFile)
+
+        then:
+        succeeds('listJars')
+
+        where:
+        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
+    }
+
+    @Unroll
+    public void "can resolve dependencies from #authScheme authenticated HTTP maven repository"() {
+        server.start()
+        given:
+        def moduleA = mavenRepo().module('group', 'projectA', '1.2').publish()
+        mavenRepo().module('group', 'projectB', '2.0').publish()
+        mavenRepo().module('group', 'projectB', '2.2').publish()
+        def moduleB = mavenRepo().module('group', 'projectB', '2.3').publish()
+        mavenRepo().module('group', 'projectB', '3.0').publish()
+        def moduleC = mavenRepo().module('group', 'projectC', '3.1-SNAPSHOT').publish()
+        def moduleD = mavenRepo().module('group', 'projectD', '4-SNAPSHOT').withNonUniqueSnapshots().publish()
+        and:
+        buildFile << """
+repositories {
+    maven {
+        url "http://localhost:${server.port}/repo"
+
+        credentials {
+            password 'password'
+            username 'username'
+        }
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+    compile 'group:projectB:2.+'
+    compile 'group:projectC:3.1-SNAPSHOT'
+    compile 'group:projectD:4-SNAPSHOT'
+}
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar', 'projectB-2.3.jar', 'projectC-3.1-SNAPSHOT.jar', 'projectD-4-SNAPSHOT.jar']
+}
+"""
+
+        when:
+        server.authenticationScheme = authScheme
+
+        and:
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.pom', 'username', 'password', moduleA.pomFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', 'username', 'password', moduleA.artifactFile)
+        server.expectGet('/repo/group/projectB/maven-metadata.xml', 'username', 'password', moduleB.rootMetaDataFile)
+        server.expectGet('/repo/group/projectB/2.3/projectB-2.3.pom', 'username', 'password', moduleB.pomFile)
+        server.expectGet('/repo/group/projectB/2.3/projectB-2.3.jar', 'username', 'password', moduleB.artifactFile)
+
+        server.expectGet('/repo/group/projectC/3.1-SNAPSHOT/maven-metadata.xml', 'username', 'password', moduleC.metaDataFile)
+        server.expectGet("/repo/group/projectC/3.1-SNAPSHOT/projectC-${moduleC.getPublishArtifactVersion()}.pom", 'username', 'password', moduleC.pomFile)
+        server.expectGet("/repo/group/projectC/3.1-SNAPSHOT/projectC-${moduleC.getPublishArtifactVersion()}.jar", 'username', 'password', moduleC.artifactFile)
+
+        server.expectGet('/repo/group/projectD/4-SNAPSHOT/maven-metadata.xml', 'username', 'password', moduleD.metaDataFile)
+        server.expectGet("/repo/group/projectD/4-SNAPSHOT/projectD-4-SNAPSHOT.pom", 'username', 'password', moduleD.pomFile)
+        server.expectGet("/repo/group/projectD/4-SNAPSHOT/projectD-4-SNAPSHOT.jar", 'username', 'password', moduleD.artifactFile)
+
+        then:
+        succeeds('listJars')
+
+        where:
+        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
+    }
+
+    @Unroll
+    def "reports failure resolving with #credsName credentials from #authScheme authenticated HTTP ivy repository"() {
+        server.start()
+        given:
+        def module = ivyRepo().module('group', 'projectA', '1.2').publish()
+        when:
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+   repositories {
+       ivy {
+            url "http://localhost:${server.port}/repo"
+            $creds
+        }
+   }
+   configurations { compile }
+   dependencies {
+       compile 'group:projectA:1.2'
+   }
+   task listJars << {
+       assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+   }
+   """
+
+        and:
+        server.authenticationScheme = authScheme
+        server.allowGetOrHead('/repo/group/projectA/1.2/ivy-1.2.xml', 'username', 'password', module.ivyFile)
+
+        then:
+        fails 'listJars'
+
+        and:
+        failure.assertHasDescription('Execution failed for task \':listJars\'.')
+        failure.assertHasCause('Could not resolve all dependencies for configuration \':compile\'.')
+        failure.assertThatCause(Matchers.containsString('Received status code 401 from server: Unauthorized'))
+
+        where:
+        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST, HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
+        credsName << ['empty', 'empty', 'bad', 'bad']
+        creds << ['', '', badCredentials, badCredentials]
+    }
+
+    @Unroll
+    def "reports failure resolving with #credsName credentials from #authScheme authenticated HTTP maven repository"() {
+        given:
+        server.start()
+
+        and:
+        def module = mavenRepo().module('group', 'projectA', '1.2').publish()
+
+        when:
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+ repositories {
+     maven {
+         url "http://localhost:${server.port}/repo"
+        $creds
+     }
+ }
+ configurations { compile }
+ dependencies {
+     compile 'group:projectA:1.2'
+ }
+ task listJars << {
+     assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+ }
+ """
+
+        and:
+        server.authenticationScheme = authScheme
+        server.allowGetOrHead('/repo/group/projectA/1.2/projectA-1.2.pom', 'username', 'password', module.pomFile)
+
+        then:
+        fails 'listJars'
+
+        and:
+        failure.assertHasDescription('Execution failed for task \':listJars\'.')
+        failure.assertHasCause('Could not resolve all dependencies for configuration \':compile\'.')
+        failure.assertThatCause(Matchers.containsString('Received status code 401 from server: Unauthorized'))
+
+        where:
+        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST, HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
+        credsName << ['empty', 'empty', 'bad', 'bad']
+        creds << ['', '', badCredentials, badCredentials]
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpEncodingDependencyResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpEncodingDependencyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..2d31405
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpEncodingDependencyResolutionIntegrationTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.http
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest;
+
+
+public class HttpEncodingDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+    public void "handles gzip encoded content"() {
+        server.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+        when:
+        server.expectGetGZipped('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGetGZipped('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds('listJars')
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpProxyResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpProxyResolveIntegrationTest.groovy
new file mode 100644
index 0000000..8fc1a64
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpProxyResolveIntegrationTest.groovy
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.http
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.gradle.test.fixtures.server.http.TestProxyServer
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+import spock.lang.Unroll
+
+class HttpProxyResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    @Rule TestProxyServer proxyServer = new TestProxyServer(server)
+    @Rule SetSystemProperties systemProperties = new SetSystemProperties()
+
+    public void "uses configured proxy to access remote HTTP repository"() {
+        server.start()
+        proxyServer.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://not.a.real.domain/repo" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+        when:
+        executer.withArguments("-Dhttp.proxyHost=localhost", "-Dhttp.proxyPort=${proxyServer.port}")
+
+        and:
+        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds('listJars')
+
+        and:
+        proxyServer.requestCount == 2
+    }
+
+    public void "uses authenticated proxy to access remote HTTP repository"() {
+        server.start()
+        proxyServer.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://not.a.real.domain/repo"
+    }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+        when:
+        executer.withArguments("-Dhttp.proxyHost=localhost", "-Dhttp.proxyPort=${proxyServer.port}", "-Dhttp.nonProxyHosts=foo",
+                               "-Dhttp.proxyUser=proxyUser", "-Dhttp.proxyPassword=proxyPassword")
+
+        and:
+        proxyServer.requireAuthentication('proxyUser', 'proxyPassword')
+
+        and:
+        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds('listJars')
+
+        and:
+        proxyServer.requestCount == 2
+    }
+
+    @Unroll
+    public void "passes target credentials to #authScheme authenticated server via proxy"() {
+        server.start()
+        proxyServer.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://not.a.real.domain/repo"
+        credentials {
+            username 'targetUser'
+            password 'targetPassword'
+        }
+    }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+        when:
+        server.authenticationScheme = authScheme
+        executer.withArguments("-Dhttp.proxyHost=localhost", "-Dhttp.proxyPort=${proxyServer.port}", "-Dhttp.proxyUser=proxyUser", "-Dhttp.proxyPassword=proxyPassword")
+
+        and:
+        proxyServer.requireAuthentication('proxyUser', 'proxyPassword')
+
+        and:
+        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', 'targetUser', 'targetPassword', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', 'targetUser', 'targetPassword', module.jarFile)
+
+        then:
+        succeeds('listJars')
+
+        and:
+        // 1 extra request for authentication
+        proxyServer.requestCount == 3
+
+        where:
+        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpRedirectResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpRedirectResolveIntegrationTest.groovy
new file mode 100644
index 0000000..0e94c08
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpRedirectResolveIntegrationTest.groovy
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.http
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+import spock.lang.Issue
+
+class HttpRedirectResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    @Rule SetSystemProperties systemProperties = new SetSystemProperties()
+    @Rule public final HttpServer server2 = new HttpServer()
+
+    public void "resolves module artifacts via HTTP redirect"() {
+        server.start()
+        server2.start()
+
+        given:
+        def module = ivyRepo().module('group', 'projectA').publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.0' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
+}
+"""
+
+        when:
+        server.expectGetRedirected('/repo/group/projectA/1.0/ivy-1.0.xml', "http://localhost:${server2.port}/redirected/group/projectA/1.0/ivy-1.0.xml")
+        server2.expectGet('/redirected/group/projectA/1.0/ivy-1.0.xml', module.ivyFile)
+        server.expectGetRedirected('/repo/group/projectA/1.0/projectA-1.0.jar', "http://localhost:${server2.port}/redirected/group/projectA/1.0/projectA-1.0.jar")
+        server2.expectGet('/redirected/group/projectA/1.0/projectA-1.0.jar', module.jarFile)
+
+        then:
+        succeeds('listJars')
+    }
+
+    @Issue('GRADLE-2196')
+    public void "resolves artifact-only module via HTTP redirect"() {
+        server.start()
+        server2.start()
+
+        given:
+        def module = ivyRepo().module('group', 'projectA').publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.0 at zip' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.zip']
+}
+"""
+
+        when:
+        server.expectGetMissing('/repo/group/projectA/1.0/ivy-1.0.xml')
+        server.expectHeadRedirected('/repo/group/projectA/1.0/projectA-1.0.zip', "http://localhost:${server2.port}/redirected/group/projectA/1.0/projectA-1.0.zip")
+        server2.expectHead('/redirected/group/projectA/1.0/projectA-1.0.zip', module.jarFile)
+        server.expectGetRedirected('/repo/group/projectA/1.0/projectA-1.0.zip', "http://localhost:${server2.port}/redirected/group/projectA/1.0/projectA-1.0.zip")
+        server2.expectGet('/redirected/group/projectA/1.0/projectA-1.0.zip', module.jarFile)
+
+        then:
+        succeeds('listJars')
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy
new file mode 100644
index 0000000..b9630a2
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.ivy
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+import static org.hamcrest.Matchers.containsString
+
+class IvyBrokenRemoteResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    public void "reports and caches missing module"() {
+        server.start()
+
+        given:
+        def repo1 = ivyHttpRepo("repo1")
+        def repo2 = ivyHttpRepo("repo2")
+        def moduleInRepo1 = repo1.module("group", "projectA", "1.2")
+        def moduleInRepo2 = repo2.module('group', 'projectA', '1.2').publish()
+
+        buildFile << """
+repositories {
+    ivy { url "${repo1.uri}"}
+    ivy { url "${repo2.uri}"}
+}
+configurations { missing }
+dependencies {
+    missing 'group:projectA:1.2'
+}
+task showMissing << { println configurations.missing.files }
+"""
+
+        when:
+        moduleInRepo1.expectIvyGetMissing()
+        moduleInRepo1.expectJarHeadMissing()
+        moduleInRepo2.expectIvyGet()
+        moduleInRepo2.expectJarGet()
+
+        then:
+        succeeds("showMissing")
+
+        when:
+        server.resetExpectations() // Missing status in repo1 is cached
+        then:
+        succeeds('showMissing')
+    }
+
+    public void "reports and recovers from broken module"() {
+        server.start()
+
+        given:
+        def module = ivyHttpRepo.module('group', 'projectA', '1.3').publish()
+
+        buildFile << """
+repositories {
+    ivy {
+        url "${ivyHttpRepo.uri}"
+    }
+}
+configurations { broken }
+dependencies {
+    broken 'group:projectA:1.3'
+}
+task showBroken << { println configurations.broken.files }
+"""
+
+        when:
+        module.expectIvyGetBroken()
+        fails("showBroken")
+
+        then:
+        failure.assertHasDescription('Execution failed for task \':showBroken\'.')
+        failure.assertHasCause('Could not resolve all dependencies for configuration \':broken\'.')
+        failure.assertHasCause('Could not resolve group:projectA:1.3.')
+        failure.assertHasCause("Could not GET '${ivyHttpRepo.uri}/group/projectA/1.3/ivy-1.3.xml'. Received status code 500 from server: broken")
+
+        when:
+        server.resetExpectations()
+        module.expectIvyGet()
+        module.expectJarGet()
+
+        then:
+        succeeds("showBroken")
+    }
+
+    public void "reports and caches missing artifacts"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "${ivyHttpRepo.uri}"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        and:
+        def module = ivyHttpRepo.module('group', 'projectA', '1.2').publish()
+
+        when:
+        module.expectIvyGet()
+        module.expectJarGetMissing()
+
+        then:
+        fails "retrieve"
+
+        failure.assertThatCause(containsString("Artifact 'group:projectA:1.2 at jar' not found"))
+
+        when:
+        server.resetExpectations()
+
+        then:
+        fails "retrieve"
+        failure.assertThatCause(containsString("Artifact 'group:projectA:1.2 at jar' not found"))
+    }
+
+    public void "reports and recovers from failed artifact download"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "${ivyHttpRepo.uri}"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        and:
+        def module = ivyHttpRepo.module('group', 'projectA', '1.2').publish()
+
+        when:
+        module.expectIvyGet()
+        module.expectJarGetBroken()
+
+        then:
+        fails "retrieve"
+        failure.assertHasCause("Could not download artifact 'group:projectA:1.2 at jar'")
+        failure.assertHasCause("Could not GET '${ivyHttpRepo.uri}/group/projectA/1.2/projectA-1.2.jar'. Received status code 500 from server: broken")
+
+        when:
+        server.resetExpectations()
+        module.expectJarGet()
+
+        then:
+        succeeds "retrieve"
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolveIntegrationTest.groovy
new file mode 100644
index 0000000..2bcdea5
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolveIntegrationTest.groovy
@@ -0,0 +1,425 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.ivy
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+class IvyChangingModuleRemoteResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "detects changed module descriptor when flagged as changing"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "${ivyHttpRepo.uri}" }
+}
+
+configurations { compile }
+
+configurations.all {
+    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.1", changing: true
+}
+
+task retrieve(type: Copy) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        when: "Version 1.1 is published"
+        def module = ivyHttpRepo.module("group", "projectA", "1.1").publish()
+
+        and: "Server handles requests"
+        module.expectIvyGet()
+        module.expectJarGet()
+
+        and: "We request 1.1 (changing)"
+        run 'retrieve'
+
+        then: "Version 1.1 jar is downloaded"
+        file('build').assertHasDescendants('projectA-1.1.jar')
+
+        when: "Module meta-data is changed (new artifact)"
+        module.artifact([name: 'other'])
+        module.dependsOn("group", "projectB", "2.0")
+        module.publish()
+        def moduleB = ivyHttpRepo.module("group", "projectB", "2.0").publish()
+
+        and: "Server handles requests"
+        server.resetExpectations()
+        // Server will be hit to get updated versions
+        module.expectIvyHead()
+        module.expectIvySha1Get()
+        module.expectIvyGet()
+        module.expectJarHead()
+        module.expectJarSha1Get()
+        module.expectArtifactGet('other')
+        moduleB.expectIvyGet()
+        moduleB.expectJarGet()
+
+        and: "We request 1.1 again"
+        run 'retrieve'
+
+        then: "We get all artifacts, including the new ones"
+        file('build').assertHasDescendants('projectA-1.1.jar', 'other-1.1.jar', 'projectB-2.0.jar')
+    }
+
+    def "can mark a module as changing after first retrieval"() {
+        server.start()
+
+        given:
+        buildFile << """
+def isChanging = project.hasProperty('isChanging') ? true : false
+repositories {
+    ivy { url "${ivyHttpRepo.uri}" }
+}
+
+configurations { compile }
+configurations.compile.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.1", changing: isChanging
+}
+
+task retrieve(type: Copy) {
+    into 'build'
+    from configurations.compile
+}
+"""
+        and:
+        def module = ivyHttpRepo.module("group", "projectA", "1.1").publish()
+        module.allowAll()
+
+        when: 'original retrieve'
+        run 'retrieve'
+
+        then:
+        def jarSnapshot = file('build/projectA-1.1.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        module.publishWithChangedContent()
+        module.expectIvyHead()
+        module.expectIvySha1Get()
+        module.expectIvyGet()
+        module.expectJarHead()
+        module.expectJarSha1Get()
+        module.expectJarGet()
+
+        and:
+        executer.withArguments('-PisChanging')
+        run 'retrieve'
+
+        then:
+        file('build/projectA-1.1.jar').assertHasChangedSince(jarSnapshot)
+    }
+
+    def "detects changed artifact when flagged as changing"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "${ivyHttpRepo.uri}" }
+}
+
+configurations { compile }
+
+configurations.all {
+    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.1", changing: true
+}
+
+task retrieve(type: Copy) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        and:
+        def module = ivyHttpRepo.module("group", "projectA", "1.1").publish()
+
+        when:
+        module.expectIvyGet()
+        module.expectJarGet()
+
+        run 'retrieve'
+
+        then:
+        def jarFile = file('build/projectA-1.1.jar')
+        jarFile.assertIsCopyOf(module.jarFile)
+        def snapshot = jarFile.snapshot()
+
+        when:
+        module.publishWithChangedContent()
+
+        server.resetExpectations()
+        // Server will be hit to get updated versions
+        module.expectIvyHead()
+        module.expectIvySha1Get()
+        module.expectIvyGet()
+        module.expectJarHead()
+        module.expectJarSha1Get()
+        module.expectJarGet()
+
+        run 'retrieve'
+
+        then:
+        def changedJarFile = file('build/projectA-1.1.jar')
+        changedJarFile.assertHasChangedSince(snapshot)
+        changedJarFile.assertIsCopyOf(module.jarFile)
+    }
+
+    def "caches changing module descriptor and artifacts until cache expiry"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "${ivyHttpRepo.uri}" }
+}
+
+configurations { compile }
+
+
+if (project.hasProperty('doNotCacheChangingModules')) {
+    configurations.all {
+        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+    }
+}
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.1", changing: true
+}
+
+task retrieve(type: Copy) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        when: "Version 1.1 is published"
+        def module = ivyHttpRepo.module("group", "projectA", "1.1").publish()
+
+        and: "Server handles requests"
+        module.expectIvyGet()
+        module.expectJarGet()
+
+        and: "We request 1.1 (changing)"
+        run 'retrieve'
+
+        then: "Version 1.1 jar is downloaded"
+        file('build').assertHasDescendants('projectA-1.1.jar')
+        def jarFile = file('build/projectA-1.1.jar')
+        jarFile.assertIsCopyOf(module.jarFile)
+        def snapshot = jarFile.snapshot()
+
+        when: "Module meta-data is changed and artifacts are modified"
+        server.resetExpectations()
+        module.artifact([name: 'other'])
+        module.publishWithChangedContent()
+
+        and: "We request 1.1 (changing), with module meta-data cached. No server requests."
+        run 'retrieve'
+
+        then: "Original module meta-data and artifacts are used"
+        file('build').assertHasDescendants('projectA-1.1.jar')
+        jarFile.assertHasNotChangedSince(snapshot)
+
+        when: "Server handles requests"
+        server.resetExpectations()
+        // Server will be hit to get updated versions
+        module.expectIvyHead()
+        module.expectIvySha1Get()
+        module.expectIvyGet()
+        module.expectJarHead()
+        module.expectJarSha1Get()
+        module.expectJarGet()
+        module.expectArtifactGet('other')
+
+        and: "We request 1.1 (changing) again, with zero expiry for dynamic revision cache"
+        executer.withArguments("-PdoNotCacheChangingModules")
+        run 'retrieve'
+
+        then: "We get new artifacts based on the new meta-data"
+        file('build').assertHasDescendants('projectA-1.1.jar', 'other-1.1.jar')
+        jarFile.assertHasChangedSince(snapshot)
+        jarFile.assertIsCopyOf(module.jarFile)
+    }
+
+    def "can use cache-control DSL to mimic changing pattern for ivy repository"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "${ivyHttpRepo.uri}" }
+}
+
+configurations { compile }
+
+import static java.util.concurrent.TimeUnit.SECONDS
+configurations.all {
+    resolutionStrategy.resolutionRules.with {
+        eachModule({ moduleResolve ->
+            if (moduleResolve.request.version.endsWith('-CHANGING')) {
+                moduleResolve.cacheFor(0, SECONDS)
+            }
+        } as Action)
+
+        eachArtifact({ artifactResolve ->
+            if (artifactResolve.request.moduleVersionIdentifier.version.endsWith('-CHANGING')) {
+                artifactResolve.cacheFor(0, SECONDS)
+            }
+        } as Action)
+    }
+}
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1-CHANGING"
+}
+
+task retrieve(type: Copy) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        when: "Version 1-CHANGING is published"
+        def module = ivyHttpRepo.module("group", "projectA", "1-CHANGING").publish()
+
+        and: "Server handles requests"
+        module.expectIvyGet()
+        module.expectJarGet()
+
+        and: "We request 1-CHANGING"
+        run 'retrieve'
+
+        then: "Version 1-CHANGING jar is used"
+        file('build').assertHasDescendants('projectA-1-CHANGING.jar')
+        def jarFile = file('build/projectA-1-CHANGING.jar')
+        jarFile.assertIsCopyOf(module.jarFile)
+        def snapshot = jarFile.snapshot()
+
+        when: "Module meta-data is changed and artifacts are modified"
+        module.artifact([name: 'other'])
+        module.publishWithChangedContent()
+
+        and: "Server handles requests"
+        server.resetExpectations()
+        // Server will be hit to get updated versions
+        module.expectIvyHead()
+        module.expectIvySha1Get()
+        module.expectIvyGet()
+        module.expectJarHead()
+        module.expectJarSha1Get()
+        module.expectJarGet()
+        module.expectArtifactGet('other')
+
+        and: "We request 1-CHANGING again"
+        executer.withArguments()
+        run 'retrieve'
+
+        then: "We get new artifacts based on the new meta-data"
+        file('build').assertHasDescendants('projectA-1-CHANGING.jar', 'other-1-CHANGING.jar')
+        jarFile.assertHasChangedSince(snapshot)
+        jarFile.assertIsCopyOf(module.jarFile)
+    }
+
+    def "avoid redownload unchanged artifact when no checksum available"() {
+        server.start()
+
+        given:
+        buildFile << """
+            repositories {
+                ivy { url "${ivyHttpRepo.uri}" }
+            }
+
+            configurations { compile }
+
+            configurations.all {
+                resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+            }
+
+            dependencies {
+                compile group: "group", name: "projectA", version: "1.1", changing: true
+            }
+
+            task retrieve(type: Copy) {
+                into 'build'
+                from configurations.compile
+            }
+        """
+
+        and:
+        def module = ivyHttpRepo.module("group", "projectA", "1.1").publish()
+        
+        def base = "/repo/group/projectA/1.1"
+        def ivyPath = "$base/$module.ivyFile.name"
+        def ivySha1Path = "${ivyPath}.sha1"
+        def jarPath = "$base/$module.jarFile.name"
+        def jarSha1Path = "${jarPath}.sha1"
+
+        when:
+        module.expectIvyGet()
+        module.expectJarGet()
+
+        and:
+        run 'retrieve'
+
+        then:
+        def downloadedJar = file('build/projectA-1.1.jar')
+        downloadedJar.assertIsCopyOf(module.jarFile)
+        def snapshot = downloadedJar.snapshot()
+
+        when:
+        server.resetExpectations()
+        module.expectIvyHead()
+        module.expectJarHead()
+
+        and:
+        run 'retrieve'
+
+        then:
+        downloadedJar.assertHasNotChangedSince(snapshot)
+
+        when:
+        // Do change the jar, so we can check that the new version wasn't downloaded
+        module.publishWithChangedContent()
+
+        server.resetExpectations()
+        module.expectIvyHead()
+        module.expectIvySha1GetMissing()
+        module.expectIvyGet()
+        module.expectJarHead()
+        module.expectJarSha1GetMissing()
+        module.expectJarGet()
+
+        run 'retrieve'
+
+        then:
+        downloadedJar.assertHasChangedSince(snapshot)
+        downloadedJar.assertIsCopyOf(module.jarFile)
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy
new file mode 100644
index 0000000..a4af2af
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy
@@ -0,0 +1,740 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.ivy
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+class IvyDynamicRevisionRemoteResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "uses latest version from version range and latest status"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "${ivyHttpRepo.uri}"
+    }
+}
+
+configurations { compile }
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.+"
+    compile group: "group", name: "projectB", version: "latest.integration"
+}
+
+configurations.all {
+    resolutionStrategy.cacheDynamicVersionsFor 0, "seconds"
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when: "Version 1.1 is published"
+        def projectA1 = ivyHttpRepo.module("group", "projectA", "1.1").publish()
+        ivyHttpRepo.module("group", "projectA", "2.0").publish()
+        def projectB1 = ivyHttpRepo.module("group", "projectB", "1.1").publish()
+
+        and: "Server handles requests"
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        projectA1.expectIvyGet()
+        projectA1.expectJarGet()
+        ivyHttpRepo.expectDirectoryListGet("group", "projectB")
+        projectB1.expectIvyGet()
+        projectB1.expectJarGet()
+
+        and:
+        run 'retrieve'
+
+        then: "Version 1.1 is used"
+        file('libs').assertHasDescendants('projectA-1.1.jar', 'projectB-1.1.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
+        file('libs/projectB-1.1.jar').assertIsCopyOf(projectB1.jarFile)
+
+        when: "New versions are published"
+        def projectA2 = ivyHttpRepo.module("group", "projectA", "1.2").publish()
+        def projectB2 = ivyHttpRepo.module("group", "projectB", "2.2").publish()
+
+        and: "Server handles requests"
+        server.resetExpectations()
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        projectA2.expectIvyGet()
+        projectA2.expectJarGet()
+        ivyHttpRepo.expectDirectoryListGet("group", "projectB")
+        projectB2.expectIvyGet()
+        projectB2.expectJarGet()
+
+        and:
+        run 'retrieve'
+
+        then: "New versions are used"
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-2.2.jar')
+        file('libs/projectA-1.2.jar').assertIsCopyOf(projectA2.jarFile)
+        file('libs/projectB-2.2.jar').assertIsCopyOf(projectB2.jarFile)
+    }
+
+    def "determines latest version with jar only"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+  ivy {
+      url "${ivyHttpRepo.uri}"
+  }
+}
+
+configurations { compile }
+
+dependencies {
+  compile group: "group", name: "projectA", version: "1.+"
+}
+
+task retrieve(type: Sync) {
+  from configurations.compile
+  into 'libs'
+}
+"""
+
+        when: "Version 1.1 is published"
+        def projectA11 = ivyHttpRepo.module("group", "projectA", "1.1").withNoMetaData().publish()
+        def projectA12 = ivyHttpRepo.module("group", "projectA", "1.2").withNoMetaData().publish()
+        ivyHttpRepo.module("group", "projectA", "2.0").withNoMetaData().publish()
+
+        and: "Server handles requests"
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        projectA12.expectIvyGetMissing()
+        projectA11.expectIvyGetMissing()
+
+        // TODO - Should not list twice
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        projectA12.expectJarHead()
+        projectA12.expectJarGet()
+
+        and:
+        run 'retrieve'
+
+        then: "Version 1.2 is used"
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+    }
+
+    def "uses latest version with correct status for latest.release and latest.milestone"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "${ivyHttpRepo.uri}"
+    }
+}
+
+configurations {
+    release
+    milestone
+}
+
+dependencies {
+    release group: "group", name: "projectA", version: "latest.release"
+    milestone group: "group", name: "projectA", version: "latest.milestone"
+}
+
+task retrieveRelease(type: Sync) {
+    from configurations.release
+    into 'release'
+}
+
+task retrieveMilestone(type: Sync) {
+    from configurations.milestone
+    into 'milestone'
+}
+"""
+
+        when: "Versions are published"
+        ivyHttpRepo.module("group", "projectA", "1.0").withStatus('release').publish()
+        ivyHttpRepo.module('group', 'projectA', '1.1').withStatus('milestone').publish()
+        ivyHttpRepo.module('group', 'projectA', '1.2').withStatus('integration').publish()
+        def release = ivyHttpRepo.module("group", "projectA", "2.0").withStatus('release').publish()
+        def milestone = ivyHttpRepo.module('group', 'projectA', '2.1').withStatus('milestone').publish()
+        def integration = ivyHttpRepo.module('group', 'projectA', '2.2').withStatus('integration').publish()
+
+        and: "Server handles requests"
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        integration.expectIvyGet()
+        milestone.expectIvyGet()
+        release.expectIvyGet()
+        release.expectJarGet()
+
+        and:
+        run 'retrieveRelease'
+
+        then:
+        file('release').assertHasDescendants('projectA-2.0.jar')
+
+        when:
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        integration.expectIvyHead()
+        milestone.expectIvyHead()
+        milestone.expectJarGet()
+
+        and:
+        run 'retrieveMilestone'
+
+        then:
+        file('milestone').assertHasDescendants('projectA-2.1.jar')
+    }
+
+    def "can use latest version from different remote repositories"() {
+        server.start()
+        def repo1 = ivyHttpRepo("ivy1")
+        def repo2 = ivyHttpRepo("ivy2")
+
+        given:
+        buildFile << """
+    repositories {
+        ivy {
+            url "${repo1.uri}"
+        }
+        ivy {
+            url "${repo2.uri}"
+        }
+    }
+
+    configurations {
+        milestone
+    }
+
+    dependencies {
+        milestone group: "group", name: "projectA", version: "latest.milestone"
+    }
+
+    task retrieveMilestone(type: Sync) {
+        from configurations.milestone
+        into 'milestone'
+    }
+    """
+
+        when: "Versions are published"
+        def version11 = repo1.module('group', 'projectA', '1.1').withStatus('milestone').publish()
+        def version12 = repo2.module('group', 'projectA', '1.2').withStatus('integration').publish()
+
+        and: "Server handles requests"
+        repo1.expectDirectoryListGet("group", "projectA")
+        version11.expectIvyGet()
+        version11.expectJarGet()
+        repo2.expectDirectoryListGet("group", "projectA")
+        version12.expectIvyGet()
+        // TODO - shouldn't need this
+        repo2.expectDirectoryListGet("group", "projectA")
+        // TODO - shouldn't need this
+        version12.expectJarGet()
+
+        and:
+        run 'retrieveMilestone'
+
+        then:
+        file('milestone').assertHasDescendants('projectA-1.1.jar')
+    }
+
+    def "checks new repositories before returning any cached value"() {
+        server.start()
+        def repo1 = ivyHttpRepo("repo1")
+        def repo2 = ivyHttpRepo("repo2")
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "${repo1.uri}" }
+}
+
+if (project.hasProperty('addRepo2')) {
+    repositories {
+        ivy { url "${repo2.uri}" }
+    }
+}
+
+configurations { compile }
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.+"
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when:
+        def projectA11 = repo1.module("group", "projectA", "1.1").publish()
+        def projectA12 = repo2.module("group", "projectA", "1.2").publish()
+
+        and: "Server handles requests"
+        repo1.expectDirectoryListGet("group", "projectA")
+        projectA11.expectIvyGet()
+        projectA11.expectJarGet()
+
+        and: "Retrieve with only repo1"
+        run 'retrieve'
+
+        then: "Version 1.1 is used"
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+
+        when: "Server handles requests"
+        server.resetExpectations()
+        repo2.expectDirectoryListGet("group", "projectA")
+        projectA12.expectIvyGet()
+        projectA12.expectJarGet()
+
+        and: "Retrieve with both repos"
+        executer.withArguments("-PaddRepo2")
+        run 'retrieve'
+
+        then: "Version 1.2 is used"
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+    }
+
+    def "does not cache information about broken modules"() {
+        server.start()
+        def repo1 = ivyHttpRepo("repo1")
+        def repo2 = ivyHttpRepo("repo2")
+
+        given:
+        buildFile << """
+    repositories {
+        ivy { url "${repo1.uri}" }
+        ivy { url "${repo2.uri}" }
+    }
+
+    configurations { compile }
+
+    dependencies {
+        compile group: "group", name: "projectA", version: "1.+"
+    }
+
+    task retrieve(type: Sync) {
+        from configurations.compile
+        into 'libs'
+    }
+    """
+
+        when:
+        def projectA12 = repo1.module("group", "projectA", "1.2").publish()
+        def projectA11 = repo2.module("group", "projectA", "1.1").publish()
+
+        and: "projectA is broken in repo1"
+        repo1.expectDirectoryListGetBroken("group", "projectA")
+        repo2.expectDirectoryListGet("group", "projectA")
+        projectA11.expectIvyGet()
+        projectA11.expectJarGet()
+
+        and: "Retrieve with only repo2"
+        run 'retrieve'
+
+        then: "Version 1.1 is used"
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+
+        when: "Server handles requests"
+        server.resetExpectations()
+        repo1.expectDirectoryListGet("group", "projectA")
+        projectA12.expectIvyGet()
+        projectA12.expectJarGet()
+
+        and: "Retrieve with both repos"
+        run 'retrieve'
+
+        then: "Version 1.2 is used"
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+    }
+
+    def "uses and caches latest of versions obtained from multiple HTTP repositories"() {
+        server.start()
+        def repo1 = ivyHttpRepo("repo1")
+        def repo2 = ivyHttpRepo("repo2")
+        def repo3 = ivyHttpRepo("repo3")
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "${repo1.uri}" }
+    ivy { url "${repo2.uri}" }
+    ivy { url "${repo3.uri}" }
+}
+
+configurations { compile }
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.+"
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when: "Versions are published"
+        def projectA11 = repo1.module("group", "projectA", "1.1").publish()
+        def projectA12 = repo3.module("group", "projectA", "1.2").publish()
+
+        and: "Server handles requests"
+        repo1.expectDirectoryListGet("group", "projectA")
+        // TODO Should not need to get this
+        projectA11.expectIvyGet()
+        // TODO Should only list missing directory once
+        repo2.expectDirectoryListGet("group", "projectA")
+        repo2.expectDirectoryListGet("group", "projectA")
+        repo3.expectDirectoryListGet("group", "projectA")
+        projectA12.expectIvyGet()
+        projectA12.expectJarGet()
+
+        and:
+        run 'retrieve'
+
+        then: "Version 1.2 is used"
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+
+        when: "Run again with cached dependencies"
+        server.resetExpectations()
+        def result = run 'retrieve'
+
+        then: "No server requests, task skipped"
+        result.assertTaskSkipped(':retrieve')
+    }
+
+    def "reuses cached artifacts that match multiple dynamic versions"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "${ivyHttpRepo.uri}" }
+}
+
+configurations { deps1; deps2 }
+
+dependencies {
+    deps1 group: "org.test", name: "projectA", version: "1.+"
+    deps2 group: "org.test", name: "projectA", version: "[1.0,2.0)"
+}
+
+task retrieve1(type: Sync) {
+    from configurations.deps1
+    into 'libs1'
+}
+task retrieve2(type: Sync) {
+    from configurations.deps2
+    into 'libs2'
+}
+"""
+
+        when:
+        ivyHttpRepo.module("org.test", "projectA", "1.1").publish()
+        def projectA12 = ivyHttpRepo.module("org.test", "projectA", "1.2").publish()
+
+        and:
+        ivyHttpRepo.expectDirectoryListGet("org.test", "projectA")
+        projectA12.expectIvyGet()
+        projectA12.expectJarGet()
+
+        and:
+        run 'retrieve1'
+
+        then:
+        file('libs1').assertHasDescendants('projectA-1.2.jar')
+
+        when:
+        server.resetExpectations()
+        ivyHttpRepo.expectDirectoryListGet("org.test", "projectA")
+        projectA12.expectIvyHead()
+
+        and:
+        run 'retrieve2'
+
+        then:
+        file('libs1').assertHasDescendants('projectA-1.2.jar')
+    }
+
+    def "caches resolved revisions until cache expiry"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "${ivyHttpRepo.uri}"
+    }
+}
+
+configurations { compile }
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.+"
+}
+
+if (project.hasProperty('noDynamicRevisionCache')) {
+    configurations.all {
+        resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
+    }
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when: "Version 1.1 is published"
+        def version1 = ivyHttpRepo.module("group", "projectA", "1.1").publish()
+
+        and: "Server handles requests"
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        version1.expectIvyGet()
+        version1.expectJarGet()
+
+        and: "We request 1.+"
+        run 'retrieve'
+
+        then: "Version 1.1 is used"
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(version1.jarFile)
+
+        when: "Version 1.2 is published"
+        def version2 = ivyHttpRepo.module("group", "projectA", "1.2").publish()
+
+        and: "We request 1.+, with dynamic mappings cached. No server requests."
+        run 'retrieve'
+
+        then: "Version 1.1 is still used, as the 1.+ -> 1.1 mapping is cached"
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(version1.jarFile)
+
+        when: "Server handles requests"
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        version2.expectIvyGet()
+        version2.expectJarGet()
+
+        and: "We request 1.+, with zero expiry for dynamic revision cache"
+        executer.withArguments("-PnoDynamicRevisionCache").withTasks('retrieve').run()
+
+        then: "Version 1.2 is used"
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+        file('libs/projectA-1.2.jar').assertIsCopyOf(version2.jarFile)
+    }
+
+    def "uses and caches dynamic revisions for transitive dependencies"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "${ivyHttpRepo.uri}"
+    }
+}
+
+configurations { compile }
+
+dependencies {
+    compile group: "group", name: "main", version: "1.0"
+}
+
+if (project.hasProperty('noDynamicRevisionCache')) {
+    configurations.all {
+        resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
+    }
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when: "Version is published"
+        def mainProject = ivyHttpRepo.module("group", "main", "1.0")
+        mainProject.dependsOn("group", "projectA", "1.+")
+        mainProject.dependsOn("group", "projectB", "latest.integration")
+        mainProject.publish()
+
+        and: "transitive dependencies have initial values"
+        def projectA1 = ivyHttpRepo.module("group", "projectA", "1.1").publish()
+        def projectB1 = ivyHttpRepo.module("group", "projectB", "1.1").publish()
+
+        and: "Server handles requests"
+        mainProject.expectIvyGet()
+        mainProject.expectJarGet()
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        projectA1.expectIvyGet()
+        projectA1.expectJarGet()
+        ivyHttpRepo.expectDirectoryListGet("group", "projectB")
+        projectB1.expectIvyGet()
+        projectB1.expectJarGet()
+
+        and:
+        run 'retrieve'
+
+        then: "Initial transitive dependencies are used"
+        file('libs').assertHasDescendants('main-1.0.jar', 'projectA-1.1.jar', 'projectB-1.1.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
+        file('libs/projectB-1.1.jar').assertIsCopyOf(projectB1.jarFile)
+
+        when: "New versions are published"
+        def projectA2 = ivyHttpRepo.module("group", "projectA", "1.2").publish()
+        def projectB2 = ivyHttpRepo.module("group", "projectB", "2.2").publish()
+
+        and: "No server requests"
+        server.resetExpectations()
+
+        and:
+        run 'retrieve'
+
+        then: "Cached versions are used"
+        file('libs').assertHasDescendants('main-1.0.jar', 'projectA-1.1.jar', 'projectB-1.1.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
+        file('libs/projectB-1.1.jar').assertIsCopyOf(projectB1.jarFile)
+
+        when: "Server handles requests"
+        server.resetExpectations()
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        projectA2.expectIvyGet()
+        projectA2.expectJarGet()
+        ivyHttpRepo.expectDirectoryListGet("group", "projectB")
+        projectB2.expectIvyGet()
+        projectB2.expectJarGet()
+
+        and: "DynamicRevisionCache is bypassed"
+        executer.withArguments("-PnoDynamicRevisionCache")
+        run 'retrieve'
+
+        then: "New versions are used"
+        file('libs').assertHasDescendants('main-1.0.jar', 'projectA-1.2.jar', 'projectB-2.2.jar')
+        file('libs/projectA-1.2.jar').assertIsCopyOf(projectA2.jarFile)
+        file('libs/projectB-2.2.jar').assertIsCopyOf(projectB2.jarFile)
+    }
+
+    public void "resolves dynamic version with 2 repositories where first repo results in 404 for directory listing"() {
+        server.start()
+        given:
+        def repo1 = ivyHttpRepo("repo1")
+        def repo2 = ivyHttpRepo("repo2")
+        def moduleA = repo2.module('group', 'projectA').publish()
+
+        and:
+        buildFile << """
+            repositories {
+                ivy { url "${repo1.uri}" }
+                ivy { url "${repo2.uri}" }
+            }
+            configurations { compile }
+            dependencies {
+                compile 'group:projectA:1.+'
+            }
+            task listJars << {
+                assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
+            }
+            """
+
+        when:
+        repo1.expectDirectoryListGetMissing("group", "projectA")
+        // TODO - should only list versions once
+        repo1.expectDirectoryListGetMissing("group", "projectA")
+        repo2.expectDirectoryListGet("group", "projectA")
+        moduleA.expectIvyGet()
+        moduleA.expectJarGet()
+
+        then:
+        succeeds('listJars')
+
+        when:
+        server.resetExpectations()
+        // No extra calls for cached dependencies
+        then:
+        succeeds('listJars')
+    }
+
+    def "reuses cached artifacts across repository types"() {
+        server.start()
+        def ivyRepo = ivyHttpRepo('repo1')
+        def mavenRepo = mavenHttpRepo('repo2')
+        def ivyModule = ivyRepo.module("org.test", "a", "1.1").publish()
+        def mavenModule = mavenRepo.module("org.test", "a", "1.1").publish()
+        assert ivyModule.jarFile.bytes == mavenModule.artifactFile.bytes
+
+        given:
+        buildFile.text = """
+repositories {
+    ivy { url '${ivyRepo.uri}' }
+}
+
+configurations { compile }
+
+dependencies {
+    compile 'org.test:a:1+'
+}
+
+task retrieve(type: Sync) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        when:
+        ivyRepo.expectDirectoryListGet("org.test", "a")
+        ivyModule.expectIvyGet()
+        ivyModule.expectJarGet()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('build').assertHasDescendants('a-1.1.jar')
+
+        when:
+        buildFile.text = """
+repositories {
+    maven { url '${mavenRepo.uri}' }
+}
+
+configurations { compile }
+
+dependencies {
+    compile 'org.test:a:[1.0,2.0)'
+}
+
+task retrieve(type: Sync) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        and:
+        mavenRepo.expectMetaDataGet("org.test", "a")
+        mavenModule.pom.expectGet()
+        mavenModule.artifact.expectHead()
+        mavenModule.artifact.sha1.expectGet()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('build').assertHasDescendants('a-1.1.jar')
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionResolveIntegrationTest.groovy
new file mode 100644
index 0000000..b3d4176
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionResolveIntegrationTest.groovy
@@ -0,0 +1,333 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.ivy
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import spock.lang.Ignore
+import spock.lang.Issue
+
+class IvyDynamicRevisionResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    @Ignore
+    @Issue("GRADLE-2502")
+    def "latest.integration selects highest version regardless of status"() {
+        given:
+        buildFile << """
+  repositories {
+      ivy {
+          url "${ivyRepo.uri}"
+      }
+  }
+  configurations { compile }
+  dependencies {
+      compile 'org.test:projectA:latest.integration'
+  }
+  task retrieve(type: Sync) {
+      from configurations.compile
+      into 'libs'
+  }
+  """
+
+        when:
+        runAndFail 'retrieve'
+
+        then:
+        failureHasCause 'Could not find any version that matches group:group, module:projectA, version:latest.integration.'
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.0').withNoMetaData().publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.0.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.1').withStatus('integration').publish()
+        ivyRepo.module('org.test', 'projectA', '1.2').withStatus('integration').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.3').withStatus('release').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.3.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.4').withNoMetaData().publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.4.jar')
+    }
+
+    @Issue("GRADLE-2502")
+    def "latest.milestone selects highest version with milestone or release status"() {
+        given:
+        buildFile << """
+  repositories {
+      ivy {
+          url "${ivyRepo.uri}"
+      }
+  }
+  configurations { compile }
+  dependencies {
+      compile 'org.test:projectA:latest.milestone'
+  }
+  task retrieve(type: Sync) {
+      from configurations.compile
+      into 'libs'
+  }
+  """
+        when:
+        runAndFail 'retrieve'
+
+        then:
+        failureHasCause 'Could not find any version that matches org.test:projectA:latest.milestone.'
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '2.0').withNoMetaData().publish()
+        runAndFail 'retrieve'
+
+        then:
+        failureHasCause 'Could not find any version that matches org.test:projectA:latest.milestone.'
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.3').withStatus('integration').publish()
+        runAndFail 'retrieve'
+
+        then:
+        failureHasCause 'Could not find any version that matches org.test:projectA:latest.milestone.'
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.0').withStatus('milestone').publish()
+        ivyRepo.module('org.test', 'projectA', '1.1').withStatus('milestone').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.2').withStatus('release').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+    }
+
+    @Issue("GRADLE-2502")
+    public void "latest.release selects highest version with release status"() {
+        given:
+        buildFile << """
+  repositories {
+      ivy {
+          url "${ivyRepo.uri}"
+      }
+  }
+  configurations { compile }
+  dependencies {
+      compile 'org.test:projectA:latest.release'
+  }
+  task retrieve(type: Sync) {
+      from configurations.compile
+      into 'libs'
+  }
+  """
+
+        when:
+        runAndFail 'retrieve'
+
+        then:
+        failureHasCause 'Could not find any version that matches org.test:projectA:latest.release.'
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '2.0').withNoMetaData().publish()
+        runAndFail 'retrieve'
+
+        then:
+        failureHasCause 'Could not find any version that matches org.test:projectA:latest.release.'
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.3').withStatus('integration').publish()
+        ivyRepo.module('org.test', 'projectA', '1.2').withStatus('milestone').publish()
+        runAndFail 'retrieve'
+
+        then:
+        failureHasCause 'Could not find any version that matches org.test:projectA:latest.release.'
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.0').withStatus('release').publish()
+        ivyRepo.module('org.test', 'projectA', '1.1').withStatus('release').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+    }
+
+    @Ignore
+    @Issue("GRADLE-2502")
+    def "version selector ending in + selects highest matching version"() {
+        given:
+        buildFile << """
+  repositories {
+      ivy {
+          url "${ivyRepo.uri}"
+      }
+  }
+  configurations { compile }
+  dependencies {
+      compile 'org.test:projectA:1.2+'
+  }
+  task retrieve(type: Sync) {
+      from configurations.compile
+      into 'libs'
+  }
+  """
+        and:
+        ivyRepo.module('org.test', 'projectA', '1.1.2').publish()
+        ivyRepo.module('org.test', 'projectA', '2.0').publish()
+
+        when:
+        runAndFail 'retrieve'
+
+        then:
+        failureHasCause 'Could not find any version that matches org.test:projectA:1.2+'
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.2').withNoMetaData().publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.2.1').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.1.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.2.9').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.9.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.2.12').withNoMetaData().publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.12.jar')
+    }
+
+    @Ignore
+    @Issue("GRADLE-2502")
+    def "version range selects highest matching version"() {
+        given:
+        buildFile << """
+  repositories {
+      ivy {
+          url "${ivyRepo.uri}"
+      }
+  }
+  configurations { compile }
+  dependencies {
+      compile 'org.test:projectA:[1.2,2.0]'
+  }
+  task retrieve(type: Sync) {
+      from configurations.compile
+      into 'libs'
+  }
+  """
+        and:
+        ivyRepo.module('org.test', 'projectA', '1.1.2').publish()
+        ivyRepo.module('org.test', 'projectA', '2.0').publish()
+
+        when:
+        runAndFail 'retrieve'
+
+        then:
+        failureHasCause 'Could not find any version that matches org.test:projectA:[1.2,2.0]'
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.2').withNoMetaData().publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.2.1').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.1.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.3').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.3.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.3.12').withNoMetaData().publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.3.12.jar')
+    }
+
+    @Issue("GRADLE-2502")
+    def "can resolve dynamic version from different repositories"() {
+        given:
+        def repo1 = ivyRepo("ivyRepo1")
+        def repo2 = ivyRepo("ivyRepo2")
+        repo1.module('org.test', 'projectA', '1.1').withStatus("milestone").publish()
+        repo2.module('org.test', 'projectA', '1.2').withStatus("integration").publish()
+
+        and:
+        buildFile << """
+  repositories {
+      ivy {
+          url "${repo1.uri}"
+      }
+      ivy {
+          url "${repo2.uri}"
+      }
+
+  }
+  configurations { compile }
+  dependencies {
+      compile 'org.test:projectA:latest.milestone'
+  }
+  task retrieve(type: Sync) {
+      from configurations.compile
+      into 'libs'
+  }
+  """
+
+        when:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyFileRepoResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyFileRepoResolveIntegrationTest.groovy
new file mode 100644
index 0000000..7833d8d
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyFileRepoResolveIntegrationTest.groovy
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.ivy
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+class IvyFileRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    public void "does not cache local artifacts or metadata"() {
+        given:
+        def repo = ivyRepo()
+        def moduleA = repo.module('group', 'projectA', '1.2')
+        moduleA.publish()
+        def moduleB = repo.module('group', 'projectB', '9-beta')
+        moduleB.publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy {
+        artifactPattern "${repo.uri}/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+        file('libs/projectA-1.2.jar').assertIsCopyOf(moduleA.jarFile)
+
+        when:
+        moduleA.dependsOn('group', 'projectB', '9-beta')
+        moduleA.publishWithChangedContent()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-9-beta.jar')
+        file('libs/projectA-1.2.jar').assertIsCopyOf(moduleA.jarFile)
+        file('libs/projectB-9-beta.jar').assertIsCopyOf(moduleB.jarFile)
+    }
+
+    public void "does not cache resolution of dynamic versions or changing modules"() {
+        def repo = ivyRepo()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        artifactPattern "${repo.uri}/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
+    }
+}
+
+configurations {
+    compile
+}
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.+"
+    compile group: "group", name: "projectB", version: "latest.integration"
+    compile group: "group", name: "projectC", version: "1.0", changing: true
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when:
+        def projectA1 = repo.module("group", "projectA", "1.1")
+        projectA1.publish()
+        def projectB1 = repo.module("group", "projectB", "1.0")
+        projectB1.publish()
+        def projectC1 = repo.module("group", "projectC", "1.0")
+        projectC1.publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.1.jar', 'projectB-1.0.jar', 'projectC-1.0.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
+        file('libs/projectB-1.0.jar').assertIsCopyOf(projectB1.jarFile)
+        def jarC = file('libs/projectC-1.0.jar')
+        jarC.assertIsCopyOf(projectC1.jarFile)
+        def jarCsnapshot = jarC.snapshot()
+
+        when:
+        def projectA2 = repo.module("group", "projectA", "1.2")
+        projectA2.publish()
+        def projectB2 = repo.module("group", "projectB", "2.0")
+        projectB2.publish()
+        projectC1.publishWithChangedContent()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-2.0.jar', 'projectC-1.0.jar')
+        file('libs/projectA-1.2.jar').assertIsCopyOf(projectA2.jarFile)
+        file('libs/projectB-2.0.jar').assertIsCopyOf(projectB2.jarFile)
+
+        def jarC1 = file('libs/projectC-1.0.jar')
+        jarC1.assertIsCopyOf(projectC1.jarFile)
+        jarC1.assertHasChangedSince(jarCsnapshot)
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy
new file mode 100644
index 0000000..61d01d5
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.ivy
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
+import org.junit.Rule
+
+class IvyHttpRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    @Rule ProgressLoggingFixture progressLogger
+
+    public void "can resolve and cache dependencies from an HTTP Ivy repository"() {
+        server.start()
+        given:
+        def module = ivyHttpRepo.module('group', 'projectA', '1.2').publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "${ivyHttpRepo.uri}" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+        when:
+        module.expectIvyGet()
+        module.expectJarGet()
+
+        then:
+        succeeds 'listJars'
+        progressLogger.downloadProgressLogged("http://localhost:${server.port}/repo/group/projectA/1.2/ivy-1.2.xml")
+        progressLogger.downloadProgressLogged("http://localhost:${server.port}/repo/group/projectA/1.2/projectA-1.2.jar")
+
+        when:
+        server.resetExpectations()
+        then:
+        succeeds 'listJars'
+    }
+
+    public void "can resolve and cache artifact-only dependencies from an HTTP Ivy repository"() {
+        server.start()
+        given:
+        def module = ivyHttpRepo.module('group', 'projectA', '1.2').publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "${ivyHttpRepo.uri}" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2 at jar' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+
+        when:
+        module.expectIvyGet()
+        module.expectJarGet()
+
+        then:
+        executer.withArgument("-i")
+        succeeds('listJars')
+
+        when:
+        server.resetExpectations()
+        // No extra calls for cached dependencies
+
+        then:
+        executer.withArgument("-i")
+        succeeds('listJars')
+    }
+
+    public void "can resolve and cache dependencies from multiple HTTP Ivy repositories"() {
+        server.start()
+        given:
+        def repo1 = ivyHttpRepo("repo1")
+        def repo2 = ivyHttpRepo("repo2")
+        def moduleA = repo1.module('group', 'projectA').publish()
+        def missingModuleB = repo1.module('group', 'projectB')
+        def moduleB = repo2.module('group', 'projectB').publish()
+        def brokenModuleC = repo1.module('group', 'projectC')
+        def moduleC = repo2.module('group', 'projectC').publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "${repo1.uri}" }
+    ivy { url "${repo2.uri}" }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.0', 'group:projectB:1.0', 'group:projectC:1.0'
+}
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar', 'projectB-1.0.jar', 'projectC-1.0.jar']
+}
+"""
+
+        when:
+        moduleA.expectIvyGet()
+        moduleA.expectJarGet()
+
+        // Handles missing in repo1
+        missingModuleB.expectIvyGetMissing()
+        missingModuleB.expectJarHeadMissing()
+
+        moduleB.expectIvyGet()
+        moduleB.expectJarGet()
+
+        // Handles from broken url in repo1 (but does not cache)
+        brokenModuleC.expectIvyGetBroken()
+
+        moduleC.expectIvyGet()
+        moduleC.expectJarGet()
+
+        then:
+        succeeds('listJars')
+
+        when:
+        server.resetExpectations()
+        // Will always re-attempt a broken repository
+        brokenModuleC.expectIvyHeadBroken()
+        // No extra calls for cached dependencies
+
+        then:
+        succeeds('listJars')
+    }
+
+    public void "uses all configured patterns to resolve artifacts and caches result"() {
+        server.start()
+
+        given:
+        def module = ivyRepo().module('group', 'projectA', '1.2').publish()
+
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}/first"
+        artifactPattern "http://localhost:${server.port}/second/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"
+        artifactPattern "http://localhost:${server.port}/third/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"
+        ivyPattern "http://localhost:${server.port}/second/[module]/[revision]/ivy.xml"
+        ivyPattern "http://localhost:${server.port}/third/[module]/[revision]/ivy.xml"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task show << { println configurations.compile.files }
+"""
+
+        when:
+        server.expectGetMissing('/first/group/projectA/1.2/ivy-1.2.xml')
+        server.expectGetMissing('/first/group/projectA/1.2/projectA-1.2.jar')
+        server.expectGetMissing('/second/projectA/1.2/ivy.xml')
+        server.expectGetMissing('/second/projectA/1.2/projectA-1.2.jar')
+        server.expectGet('/third/projectA/1.2/ivy.xml', module.ivyFile)
+        server.expectGet('/third/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds('show')
+
+        when:
+        server.resetExpectations()
+
+        then:
+        succeeds('show')
+    }
+
+    public void "replaces org.name with org/name when using maven layout"() {
+        server.start()
+
+        given:
+        def module = ivyRepo().module('org.name.here', 'projectA', '1.2').publish()
+
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}"
+        layout "maven"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'org.name.here:projectA:1.2'
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when:
+        server.expectGet('/org/name/here/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGet('/org/name/here/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        and:
+        succeeds('retrieve')
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpsRepoResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpsRepoResolveIntegrationTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpsRepoResolveIntegrationTest.groovy
rename to subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpsRepoResolveIntegrationTest.groovy
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyResolveIntegrationTest.groovy
new file mode 100644
index 0000000..2d81061
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyResolveIntegrationTest.groovy
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.ivy
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+class IvyResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "dependency includes all artifacts and transitive dependencies of referenced configuration"() {
+        given:
+        def repo = ivyRepo()
+        def module = repo.module("org.gradle", "test", "1.45")
+        module.dependsOn("org.gradle", "other", "preview-1")
+        module.artifact(classifier: "classifier")
+        module.artifact(name: "test-extra")
+        module.publish()
+        repo.module("org.gradle", "other", "preview-1").publish()
+
+        and:
+        buildFile << """
+repositories { ivy { url "${repo.uri}" } }
+configurations { compile }
+dependencies {
+    compile "org.gradle:test:1.45"
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['test-1.45.jar', 'test-1.45-classifier.jar', 'test-extra-1.45.jar', 'other-preview-1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    def "dependency that references a classifier includes the matching artifact only plus the transitive dependencies of referenced configuration"() {
+        given:
+        def repo = ivyRepo()
+        repo.module("org.gradle", "test", "1.45")
+                .dependsOn("org.gradle", "other", "preview-1")
+                .artifact(classifier: "classifier")
+                .artifact(name: "test-extra")
+                .publish()
+        repo.module("org.gradle", "other", "preview-1").publish()
+
+        and:
+        buildFile << """
+repositories { ivy { url "${repo.uri}" } }
+configurations { compile }
+dependencies {
+    compile "org.gradle:test:1.45:classifier"
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['test-1.45-classifier.jar', 'other-preview-1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    def "dependency that references an artifact includes the matching artifact only plus the transitive dependencies of referenced configuration"() {
+        given:
+        def repo = ivyRepo()
+        def module = repo.module("org.gradle", "test", "1.45")
+        module.dependsOn("org.gradle", "other", "preview-1")
+        module.artifact(classifier: "classifier")
+        module.artifact(name: "test-extra")
+        module.publish()
+        repo.module("org.gradle", "other", "preview-1").publish()
+
+        and:
+        buildFile << """
+repositories { ivy { url "${repo.uri}" } }
+configurations { compile }
+dependencies {
+    compile ("org.gradle:test:1.45") {
+        artifact {
+            name = 'test-extra'
+            type = 'jar'
+        }
+    }
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['test-extra-1.45.jar', 'other-preview-1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    def "transitive flag of referenced configuration affects its transitive dependencies only"() {
+        given:
+        def repo = ivyRepo()
+        def module = repo.module("org.gradle", "test", "1.45")
+        module.dependsOn("org.gradle", "other", "preview-1")
+        module.nonTransitive('default')
+        module.publish()
+        repo.module("org.gradle", "other", "preview-1").dependsOn("org.gradle", "other2", "7").publish()
+        repo.module("org.gradle", "other2", "7").publish()
+
+        and:
+        buildFile << """
+repositories { ivy { url "${repo.uri}" } }
+configurations {
+    compile
+    runtime.extendsFrom compile
+}
+dependencies {
+    compile "org.gradle:test:1.45"
+    runtime "org.gradle:other:preview-1"
+}
+
+task check << {
+    def spec = { it.name == 'test' } as Spec
+
+    assert configurations.compile.collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
+    assert configurations.compile.resolvedConfiguration.getFiles(spec).collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
+
+    assert configurations.runtime.collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar', 'other2-7.jar']
+    assert configurations.compile.resolvedConfiguration.getFiles(spec).collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileResolveIntegrationTest.groovy
new file mode 100644
index 0000000..c871a5d
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileResolveIntegrationTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import spock.lang.Issue
+
+class BadPomFileResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-1005")
+    def "can handle self referencing dependency"() {
+        given:
+        file("settings.gradle") << "include 'client'"
+
+        and:
+        mavenRepo().module('group', 'artifact', '1.0').dependsOn('group', 'artifact', '1.0').publish()
+
+        and:
+        buildFile << """
+            repositories {
+                maven { url "${mavenRepo().uri}" }
+            }
+            configurations { compile }
+            dependencies {
+                compile "group:artifact:1.0"
+            }
+            task libs << { assert configurations.compile.files.collect {it.name} == ['artifact-1.0.jar'] }
+        """
+
+        expect:
+        succeeds ":libs"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/LegacyMavenRepoResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/LegacyMavenRepoResolveIntegrationTest.groovy
new file mode 100644
index 0000000..9d9470d
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/LegacyMavenRepoResolveIntegrationTest.groovy
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+class LegacyMavenRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    def "can configure legacy Maven resolver to verify artifact using checksums"() {
+        server.start()
+
+        given:
+        def module = mavenHttpRepo.module("group", "module", "1.2").publishWithChangedContent()
+        buildFile << """
+repositories {
+    def repo = mavenRepo url: '${mavenHttpRepo.uri}'
+    repo.checksums = 'sha1,md5'
+}
+
+configurations {
+    check
+}
+
+dependencies {
+    check 'group:module:1.2'
+}
+
+task check << {
+    configurations.check.files*.name == 'module-1.2.jar'
+}
+"""
+        and:
+        module.pom.expectGet()
+        module.pom.sha1.expectGetMissing()
+        module.pom.md5.expectGet()
+        module.artifact.expectGet()
+        module.artifact.sha1.expectGetMissing()
+        module.artifact.md5.expectGet()
+
+        expect:
+        succeeds 'check'
+
+        when:
+        module.publishWithChangedContent()
+
+        and:
+        server.resetExpectations()
+        module.pom.expectHead()
+        module.pom.sha1.expectGet()
+        module.pom.expectGet()
+        // TODO - shouldn't get checksum twice
+        module.pom.sha1.expectGet()
+        module.artifact.expectHead()
+        module.artifact.sha1.expectGet()
+        module.artifact.expectGet()
+        // TODO - shouldn't get checksum twice
+        module.artifact.sha1.expectGet()
+
+        then:
+        executer.withArguments("--refresh-dependencies")
+        succeeds 'check'
+    }
+
+    def "fails when checksum does not match artifact contents"() {
+        server.start()
+
+        given:
+        def module = mavenHttpRepo.module("group", "module", "1.2").publishWithChangedContent()
+        buildFile << """
+repositories {
+    def repo = mavenRepo url: '${mavenHttpRepo.uri}'
+    repo.checksums = 'sha1'
+}
+
+configurations {
+    check
+}
+
+dependencies {
+    check 'group:module:1.2'
+}
+
+task check << {
+    configurations.check.files*.name == 'module-1.2.jar'
+}
+"""
+        and:
+        module.pom.expectGet()
+        module.pom.sha1.expectGet()
+        module.artifact.expectGet()
+        module.artifact.sha1.expectGet()
+        and:
+        module.artifact.sha1.file.text = '1234'
+        expect:
+        fails 'check'
+        failureHasCause("Could not download artifact 'group:module:1.2 at jar'")
+        failureHasCause("invalid sha1: expected=1234 computed=5b253435f362abf1a12197966e332df7d2b153f5")
+    }
+
+    def "can configure resolver to fail when descriptor is not present"() {
+        server.start()
+
+        given:
+        def module = mavenHttpRepo.module("group", "module", "1.2").publish()
+
+        buildFile << """
+repositories {
+    def repo = mavenRepo url: '${mavenHttpRepo.uri}'
+    repo.descriptor = "required"
+}
+
+configurations {
+    check
+}
+
+dependencies {
+    check 'group:module:1.2'
+}
+
+task check << {
+    configurations.check.files
+}
+"""
+        and:
+        module.pom.expectGetMissing()
+
+        expect:
+        fails 'check'
+        failureHasCause("Could not find group:module:1.2.")
+    }
+
+    def "can configure resolver to ignore poms"() {
+        server.start()
+
+        given:
+        def module = mavenHttpRepo.module("group", "module", "1.2").publish()
+
+        buildFile << """
+repositories {
+    def repo = mavenRepo url: '${mavenHttpRepo.uri}'
+    repo.usepoms = false
+}
+
+configurations {
+    check
+}
+
+dependencies {
+    check 'group:module:1.2'
+}
+
+task check << {
+    configurations.check.files*.name == 'module-1.2.jar'
+}
+"""
+        and:
+        // TODO - do not need this head request
+        module.artifact.expectHead()
+        module.artifact.expectGet()
+
+        expect:
+        succeeds "check"
+    }
+
+    def "can configure resolver to ignore maven-metadata.xml when resolving snapshots"() {
+        server.start()
+
+        given:
+        def module = mavenHttpRepo.module("group", "module", "1.2-SNAPSHOT").withNonUniqueSnapshots().publish()
+
+        buildFile << """
+repositories {
+    def repo = mavenRepo url: '${mavenHttpRepo.uri}'
+    repo.useMavenMetadata = false
+}
+
+configurations {
+    check
+}
+
+dependencies {
+    check 'group:module:1.2-SNAPSHOT'
+}
+
+task check << {
+    configurations.check.files*.name == 'module-1.2-SNAPSHOT.jar'
+}
+"""
+        and:
+        module.pom.expectGet()
+        module.artifact.expectGet()
+
+        expect:
+        executer.withDeprecationChecksDisabled()
+        succeeds "check"
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDependencyResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDependencyResolveIntegrationTest.groovy
new file mode 100644
index 0000000..0cc4101
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDependencyResolveIntegrationTest.groovy
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+class MavenDependencyResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    def "dependency includes main artifact and runtime dependencies of referenced module"() {
+        given:
+        def module = mavenRepo().module("org.gradle", "test", "1.45")
+        module.dependsOn("org.gradle", "other", "preview-1")
+        module.artifact(classifier: 'classifier')
+        module.publish()
+        mavenRepo().module("org.gradle", "other", "preview-1").publish()
+
+        and:
+        buildFile << """
+repositories { maven { url "${mavenRepo().uri}" } }
+configurations { compile }
+dependencies {
+    compile "org.gradle:test:1.45"
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    def "dependency that references a classifier includes the matching artifact only plus the runtime dependencies of referenced module"() {
+        given:
+        def module = mavenRepo().module("org.gradle", "test", "1.45")
+        module.dependsOn("org.gradle", "other", "preview-1")
+        module.artifact(classifier: 'classifier')
+        module.artifact(classifier: 'some-other')
+        module.publish()
+        mavenRepo().module("org.gradle", "other", "preview-1").publish()
+
+        and:
+        buildFile << """
+repositories { maven { url "${mavenRepo().uri}" } }
+configurations { compile }
+dependencies {
+    compile "org.gradle:test:1.45:classifier"
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['test-1.45-classifier.jar', 'other-preview-1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    def "dependency that references an artifact includes the matching artifact only plus the runtime dependencies of referenced module"() {
+        given:
+        def module = mavenRepo().module("org.gradle", "test", "1.45")
+        module.dependsOn("org.gradle", "other", "preview-1")
+        module.artifact(classifier: 'classifier')
+        module.publish()
+        mavenRepo().module("org.gradle", "other", "preview-1").publish()
+
+        and:
+        buildFile << """
+repositories { maven { url "${mavenRepo().uri}" } }
+configurations { compile }
+dependencies {
+    compile ("org.gradle:test:1.45") {
+        artifact {
+            name = 'test'
+            type = 'jar'
+            classifier = 'classifier'
+        }
+    }
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['test-1.45-classifier.jar', 'other-preview-1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    def "resolves dependencies on real projects"() {
+        // Hibernate core brings in conflicts, exclusions and parent poms
+        // Add a direct dependency on an earlier version of commons-collection than required by hibernate core
+        // Logback classic depends on a later version of slf4j-api than required by hibernate core
+
+        given:
+        buildFile << """
+repositories {
+    mavenCentral()
+}
+
+configurations {
+    compile
+}
+
+dependencies {
+    compile "commons-collections:commons-collections:3.0"
+    compile "ch.qos.logback:logback-classic:0.9.30"
+    compile "org.hibernate:hibernate-core:3.6.7.Final"
+}
+
+task check << {
+    def compile = configurations.compile
+    assert compile.resolvedConfiguration.firstLevelModuleDependencies.collect { it.name } == [
+        'ch.qos.logback:logback-classic:0.9.30',
+        'org.hibernate:hibernate-core:3.6.7.Final',
+        'commons-collections:commons-collections:3.1'
+    ]
+
+    def filteredDependencies = compile.resolvedConfiguration.getFirstLevelModuleDependencies({ it.name == 'logback-classic' } as Spec)
+    assert filteredDependencies.collect { it.name } == [
+        'ch.qos.logback:logback-classic:0.9.30'
+    ]
+
+    def filteredFiles = compile.resolvedConfiguration.getFiles({ it.name == 'logback-classic' } as Spec)
+    assert filteredFiles.collect { it.name } == [
+        'logback-classic-0.9.30.jar',
+         'logback-core-0.9.30.jar',
+          'slf4j-api-1.6.2.jar'
+    ]
+
+    assert compile.collect { it.name } == [
+        'logback-classic-0.9.30.jar',
+        'hibernate-core-3.6.7.Final.jar',
+        'commons-collections-3.1.jar',
+        'logback-core-0.9.30.jar',
+        'slf4j-api-1.6.2.jar',
+        'antlr-2.7.6.jar',
+        'dom4j-1.6.1.jar',
+        'hibernate-commons-annotations-3.2.0.Final.jar',
+        'hibernate-jpa-2.0-api-1.0.1.Final.jar',
+        'jta-1.1.jar'
+    ]
+
+    assert compile.resolvedConfiguration.resolvedArtifacts.collect { it.file.name } == [
+        'logback-classic-0.9.30.jar',
+        'hibernate-core-3.6.7.Final.jar',
+        'logback-core-0.9.30.jar',
+        'slf4j-api-1.6.2.jar',
+        'antlr-2.7.6.jar',
+        'dom4j-1.6.1.jar',
+        'hibernate-commons-annotations-3.2.0.Final.jar',
+        'hibernate-jpa-2.0-api-1.0.1.Final.jar',
+        'jta-1.1.jar',
+        'commons-collections-3.1.jar'
+    ]
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDynamicResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDynamicResolveIntegrationTest.groovy
new file mode 100644
index 0000000..93442a0
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDynamicResolveIntegrationTest.groovy
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+class MavenDynamicResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "can resolve dynamic version declared in pom as transitive dependency from HTTP Maven repository"() {
+        given:
+        server.start()
+
+        mavenHttpRepo.module('org.test', 'projectC', '1.1').publish()
+        def projectC = mavenHttpRepo.module('org.test', 'projectC', '1.5').publish()
+        mavenHttpRepo.module('org.test', 'projectC', '2.0').publish()
+        def projectB = mavenHttpRepo.module('org.test', 'projectB', '1.0').dependsOn("org.test", 'projectC', '[1.0, 2.0)').publish()
+        def projectA = mavenHttpRepo.module('org.test', 'projectA', '1.0').dependsOn('org.test', 'projectB', '1.0').publish()
+
+        buildFile << """
+    repositories {
+        maven { url '${mavenHttpRepo.uri}' }
+    }
+    configurations { compile }
+    dependencies {
+        compile 'org.test:projectA:1.0'
+    }
+
+    task retrieve(type: Sync) {
+        into 'libs'
+        from configurations.compile
+    }
+    """
+
+        when:
+        projectA.pom.expectGet()
+        projectA.getArtifact().expectGet()
+        projectB.pom.expectGet()
+        projectB.getArtifact().expectGet()
+        mavenHttpRepo.expectMetaDataGet("org.test", "projectC")
+        projectC.pom.expectGet()
+        projectC.getArtifact().expectGet()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.0.jar', 'projectB-1.0.jar', 'projectC-1.5.jar')
+        def snapshot = file('libs/projectA-1.0.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
+    }
+
+    def "falls back to directory listing when maven-metadata.xml is missing"() {
+        given:
+        server.start()
+        mavenHttpRepo.module('org.test', 'projectA', '1.0').publish()
+        def projectA = mavenHttpRepo.module('org.test', 'projectA', '1.5').publish()
+
+        buildFile << """
+    repositories {
+        maven { url '${mavenHttpRepo.uri}' }
+    }
+    configurations { compile }
+    dependencies {
+        compile 'org.test:projectA:1.+'
+    }
+
+    task retrieve(type: Sync) {
+        into 'libs'
+        from configurations.compile
+    }
+    """
+
+        when:
+        mavenHttpRepo.expectMetaDataGetMissing("org.test", "projectA")
+        mavenHttpRepo.expectDirectoryListGet("org.test", "projectA")
+        projectA.pom.expectGet()
+        projectA.getArtifact().expectGet()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.5.jar')
+        def snapshot = file('libs/projectA-1.5.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs/projectA-1.5.jar').assertHasNotChangedSince(snapshot)
+    }
+
+    def "does not cache broken module information"() {
+        given:
+        server.start()
+        def repo1 = mavenHttpRepo("repo1")
+        def repo2 = mavenHttpRepo("repo2")
+        def projectA1 = repo1.module('group', 'projectA', '1.1').publish()
+        def projectA2 = repo2.module('group', 'projectA', '1.5').publish()
+
+        buildFile << """
+        repositories {
+            maven { url '${repo1.uri}' }
+            maven { url '${repo2.uri}' }
+        }
+        configurations { compile }
+        dependencies {
+            compile 'group:projectA:1.+'
+        }
+
+        task retrieve(type: Sync) {
+            into 'libs'
+            from configurations.compile
+        }
+        """
+
+        when:
+        repo1.expectMetaDataGet("group", "projectA")
+        projectA1.pom.expectGet()
+        projectA1.getArtifact().expectGet()
+
+        repo2.expectMetaDataGet("group", "projectA")
+        projectA2.pom.expectGetBroken()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+
+        when:
+        server.resetExpectations()
+        repo2.expectMetaDataGet("group", "projectA")
+        projectA2.pom.expectGet()
+        projectA2.artifact.expectGet()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.5.jar')
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenFileRepoResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenFileRepoResolveIntegrationTest.groovy
new file mode 100644
index 0000000..14a377e
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenFileRepoResolveIntegrationTest.groovy
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+class MavenFileRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    public void "can resolve snapshots uncached from local Maven repository"() {
+        given:
+        def moduleA = mavenRepo().module('group', 'projectA', '1.2-SNAPSHOT')
+        def moduleB = mavenRepo().module('group', 'projectB', '9.1')
+        moduleA.publish()
+        moduleB.publish()
+
+        and:
+        buildFile << """
+configurations { compile }
+repositories { maven { url "${mavenRepo().uri}" } }
+dependencies { compile 'group:projectA:1.2-SNAPSHOT' }
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'build'
+}
+"""
+
+        when:
+        run 'retrieve'
+
+        then:
+        def buildDir = file('build')
+        buildDir.assertHasDescendants(moduleA.artifactFile.name)
+        buildDir.file(moduleA.artifactFile.name).assertIsCopyOf(moduleA.artifactFile)
+
+        when:
+        moduleA.dependsOn('group', 'projectB', '9.1')
+        moduleA.publishWithChangedContent()
+        run 'retrieve'
+
+        then:
+        buildDir.assertHasDescendants(moduleA.artifactFile.name, 'projectB-9.1.jar')
+        buildDir.file(moduleA.artifactFile.name).assertIsCopyOf(moduleA.artifactFile)
+        buildDir.file('projectB-9.1.jar').assertIsCopyOf(moduleB.artifactFile)
+    }
+
+    public void "does not cache artifacts and metadata from local Maven repository"() {
+        given:
+        def moduleA = mavenRepo().module('group', 'projectA', '1.2')
+        def moduleB = mavenRepo().module('group', 'projectB', '9.1')
+        moduleA.publish()
+        moduleB.publish()
+
+        and:
+        buildFile << """
+configurations { compile }
+repositories { maven { url "${mavenRepo().uri}" } }
+dependencies { compile 'group:projectA:1.2' }
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'build'
+}
+"""
+
+        when:
+        run 'retrieve'
+
+        then:
+        def buildDir = file('build')
+        buildDir.assertHasDescendants('projectA-1.2.jar')
+        buildDir.file('projectA-1.2.jar').assertIsCopyOf(moduleA.artifactFile)
+
+        when:
+        moduleA.dependsOn('group', 'projectB', '9.1')
+        moduleA.publishWithChangedContent()
+        run 'retrieve'
+
+        then:
+        buildDir.assertHasDescendants('projectA-1.2.jar', 'projectB-9.1.jar')
+        buildDir.file('projectA-1.2.jar').assertIsCopyOf(moduleA.artifactFile)
+        buildDir.file('projectB-9.1.jar').assertIsCopyOf(moduleB.artifactFile)
+    }
+
+    public void "uses artifactUrls to resolve artifacts"() {
+        given:
+        def moduleA = mavenRepo().module('group', 'projectA', '1.2')
+        def moduleB = mavenRepo().module('group', 'projectB', '9.1')
+        moduleA.publish()
+        moduleB.publish()
+
+        def artifactsRepo = mavenRepo('artifactsRepo')
+        // Create a module to get the correct module directory, but do not publish the module
+        def artifactsModuleA = artifactsRepo.module('group', 'projectA', '1.2')
+        moduleA.artifactFile.moveToDirectory(artifactsModuleA.moduleDir)
+
+        and:
+        buildFile << """
+repositories {
+    maven {
+        url "${mavenRepo().uri}"
+        artifactUrls "${artifactsRepo.uri}"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+    compile 'group:projectB:9.1'
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'build'
+}
+"""
+
+        when:
+        run 'retrieve'
+
+        then:
+        def buildDir = file('build')
+        buildDir.assertHasDescendants('projectA-1.2.jar', 'projectB-9.1.jar')
+        buildDir.file('projectA-1.2.jar').assertIsCopyOf(artifactsModuleA.artifactFile)
+        buildDir.file('projectB-9.1.jar').assertIsCopyOf(moduleB.artifactFile)
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy
new file mode 100644
index 0000000..6445672
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
+import org.junit.Rule
+
+class MavenHttpRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    @Rule ProgressLoggingFixture progressLogging
+
+    def "can resolve and cache dependencies from HTTP Maven repository"() {
+        given:
+        server.start()
+
+        def projectB = mavenRepo().module('group', 'projectB').publish()
+        def projectA = mavenRepo().module('group', 'projectA').dependsOn('projectB').publish()
+
+        buildFile << """
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.0'
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.0.jar', 'projectB-1.0.jar')
+        def snapshot = file('libs/projectA-1.0.jar').snapshot()
+
+        and:
+        progressLogging.downloadProgressLogged("http://localhost:${server.port}/repo1/group/projectA/1.0/projectA-1.0.pom")
+        progressLogging.downloadProgressLogged("http://localhost:${server.port}/repo1/group/projectA/1.0/projectA-1.0.jar")
+        progressLogging.downloadProgressLogged("http://localhost:${server.port}/repo1/group/projectB/1.0/projectB-1.0.pom")
+        progressLogging.downloadProgressLogged("http://localhost:${server.port}/repo1/group/projectB/1.0/projectB-1.0.jar")
+
+        when:
+        server.resetExpectations()
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
+    }
+
+    def "can resolve and cache artifact-only dependencies from an HTTP Maven repository"() {
+        server.start()
+        given:
+        def module = mavenRepo().module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    maven { url "http://localhost:${server.port}/repo1" }
+    maven { url "http://localhost:${server.port}/repo2" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2 at jar' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+        when:
+        // TODO: Should meta-data be fetched for an artifact-only dependency?
+        server.expectGetMissing('/repo1/group/projectA/1.2/projectA-1.2.pom')
+        server.expectHeadMissing('/repo1/group/projectA/1.2/projectA-1.2.jar')
+
+        server.expectGet('/repo2/group/projectA/1.2/projectA-1.2.pom', module.pomFile)
+        server.expectGet('/repo2/group/projectA/1.2/projectA-1.2.jar', module.artifactFile)
+
+        then:
+        succeeds('listJars')
+
+        when:
+        server.resetExpectations()
+        // No extra calls for cached dependencies
+
+        then:
+        succeeds('listJars')
+    }
+
+    def "can resolve and cache dependencies from multiple HTTP Maven repositories"() {
+        given:
+        server.start()
+
+        buildFile << """
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+    maven { url 'http://localhost:${server.port}/repo2' }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.0', 'group:projectB:1.0'
+}
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar', 'projectB-1.0.jar']
+}
+"""
+
+        def projectA = mavenRepo().module('group', 'projectA').publish()
+        def projectB = mavenRepo().module('group', 'projectB').publish()
+
+        when:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+
+        // Looks for POM and JAR in repo1 before looking in repo2 (jar is an attempt to handle publication without module descriptor)
+        server.expectGetMissing('/repo1/group/projectB/1.0/projectB-1.0.pom')
+        server.expectHeadMissing('/repo1/group/projectB/1.0/projectB-1.0.jar')
+        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+        then:
+        succeeds 'listJars'
+
+        when:
+        server.resetExpectations()
+        // No server requests when all jars cached
+
+        then:
+        succeeds 'listJars'
+    }
+
+    def "uses artifactsUrl to resolve artifacts"() {
+        given:
+        server.start()
+
+        buildFile << """
+repositories {
+    maven {
+        url 'http://localhost:${server.port}/repo1'
+        artifactUrls 'http://localhost:${server.port}/repo2'
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.0', 'group:projectB:1.0'
+}
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar', 'projectB-1.0.jar']
+}
+"""
+
+        def projectA = mavenRepo().module('group', 'projectA')
+        def projectB = mavenRepo().module('group', 'projectB')
+        projectA.publish()
+        projectB.publish()
+
+        when:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGetMissing('/repo1/group/projectB/1.0/projectB-1.0.jar')
+        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+        then:
+        succeeds 'listJars'
+    }
+
+    def "can resolve and cache dependencies from HTTP Maven repository with invalid settings.xml"() {
+        given:
+        server.start()
+
+        def projectB = mavenRepo().module('group', 'projectB').publish()
+        def projectA = mavenRepo().module('group', 'projectA').dependsOn('projectB').publish()
+
+        buildFile << """
+    repositories {
+        maven { url 'http://localhost:${server.port}/repo1' }
+    }
+    configurations { compile }
+    dependencies {
+        compile 'group:projectA:1.0'
+    }
+
+    task retrieve(type: Sync) {
+        into 'libs'
+        from configurations.compile
+    }
+    """
+
+        def m2Home = file("M2_REPO")
+        def settingsFile = m2Home.file("conf/settings.xml")
+        settingsFile << "invalid content... blabla"
+
+        when:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+        and:
+
+        executer.withEnvironmentVars(M2_HOME: m2Home.absolutePath)
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.0.jar', 'projectB-1.0.jar')
+        def snapshot = file('libs/projectA-1.0.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpsRepoResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpsRepoResolveIntegrationTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpsRepoResolveIntegrationTest.groovy
rename to subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpsRepoResolveIntegrationTest.groovy
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy
new file mode 100644
index 0000000..837e573
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.maven.M2Installation
+import org.gradle.test.fixtures.maven.MavenModule
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+
+import static org.hamcrest.Matchers.containsString
+
+class MavenLocalRepoResolveIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule SetSystemProperties sysProp = new SetSystemProperties()
+    M2Installation m2Installation;
+
+    public void setup() {
+        requireOwnGradleUserHomeDir()
+        buildFile << """
+                repositories {
+                    mavenLocal()
+                }
+                configurations { compile }
+                dependencies {
+                    compile 'group:projectA:1.2'
+                }
+
+                task retrieve(type: Sync) {
+                    from configurations.compile
+                    into 'build'
+                }"""
+
+        m2Installation = new M2Installation(testDirectory)
+        using m2Installation
+    }
+
+    public void "can resolve artifacts from local m2 when user settings.xml does not exist"() {
+        given:
+        def moduleA = m2Installation.mavenRepo().module('group', 'projectA', '1.2').publish()
+
+        when:
+        run 'retrieve'
+
+        then:
+        hasArtifact(moduleA)
+
+    }
+
+    public void "can resolve artifacts from local m2 with custom local repository defined in user settings.xml"() {
+        given:
+        def artifactRepo = maven("artifactrepo")
+        m2Installation.generateUserSettingsFile(artifactRepo)
+        def moduleA = artifactRepo.module('group', 'projectA', '1.2').publish()
+
+        when:
+        run 'retrieve'
+
+        then:
+        hasArtifact(moduleA)
+    }
+
+    public void "can resolve artifacts from local m2 with custom local repository defined in global settings.xml"() {
+        given:
+        def artifactRepo = maven("artifactrepo")
+        m2Installation.generateGlobalSettingsFile(artifactRepo)
+        def moduleA = artifactRepo.module('group', 'projectA', '1.2').publish()
+
+        when:
+        run 'retrieve'
+
+        then:
+        hasArtifact(moduleA)
+    }
+
+    public void "local repository in user settings take precedence over the local repository global settings"() {
+        given:
+        def globalRepo = maven("globalArtifactRepo")
+        def userRepo = maven("userArtifactRepo")
+        m2Installation.generateGlobalSettingsFile(globalRepo).generateUserSettingsFile(userRepo)
+        def moduleA = userRepo.module('group', 'projectA', '1.2').publish()
+        globalRepo.module('group', 'projectA', '1.2').publishWithChangedContent()
+
+        when:
+        run 'retrieve'
+
+        then:
+        hasArtifact(moduleA)
+    }
+
+    public void "fail with meaningful error message if settings.xml is invalid"() {
+        given:
+        m2Installation.userSettingsFile << "invalid content"
+
+        when:
+        def failure = runAndFail('retrieve')
+
+        then:
+        failure.assertThatCause(containsString(String.format("Non-parseable settings %s:", m2Installation.userSettingsFile.absolutePath)));
+    }
+
+    public void "mavenLocal is ignored if no local maven repository exists"() {
+        given:
+        def anotherRepo = maven("another-local-repo")
+        def moduleA = anotherRepo.module('group', 'projectA', '1.2').publishWithChangedContent()
+
+        and:
+        buildFile << """
+        repositories{
+            maven { url "${anotherRepo.uri}" }
+        }
+        """
+
+        when:
+        run 'retrieve'
+
+        then:
+        hasArtifact(moduleA)
+    }
+
+    def hasArtifact(MavenModule module) {
+        def buildDir = file('build')
+        def artifactName = module.artifactFile.name
+        buildDir.assertHasDescendants(artifactName)
+        buildDir.file(artifactName).assertIsCopyOf(module.artifactFile)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenParentPomResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenParentPomResolveIntegrationTest.groovy
new file mode 100644
index 0000000..4e6841c
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenParentPomResolveIntegrationTest.groovy
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import spock.lang.Issue
+
+class MavenParentPomResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "includes dependencies from parent pom"() {
+        given:
+        server.start()
+
+        def parentDep = mavenHttpRepo.module("org", "parent_dep", "1.2").publish()
+        def childDep = mavenHttpRepo.module("org", "child_dep", "1.7").publish()
+
+        def parent = mavenHttpRepo.module("org", "parent", "1.0")
+        parent.hasPackaging('pom')
+        parent.dependsOn("org", "parent_dep", "1.2")
+        parent.publish()
+
+        def child = mavenHttpRepo.module("org", "child", "1.0")
+        child.dependsOn("org", "child_dep", "1.7")
+        child.parent("org", "parent", "1.0")
+        child.publish()
+
+        buildFile << """
+repositories {
+    maven { url '${mavenHttpRepo.uri}' }
+}
+configurations { compile }
+dependencies { compile 'org:child:1.0' }
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when:
+        child.pom.expectGet()
+        parent.pom.expectGet()
+
+        // Will always check for a default artifact with a module with 'pom' packaging
+        // TODO - should not make this request
+        parent.artifact.expectHeadMissing()
+
+        child.artifact.expectGet()
+
+        parentDep.pom.expectGet()
+        parentDep.artifact.expectGet()
+        childDep.pom.expectGet()
+        childDep.artifact.expectGet()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('child-1.0.jar', 'parent_dep-1.2.jar', 'child_dep-1.7.jar')
+    }
+
+    @Issue("GRADLE-2641")
+    def "can handle parent pom with SNAPSHOT version"() {
+        given:
+        server.start()
+
+        def parent = mavenHttpRepo.module("org", "parent", "1.0-SNAPSHOT")
+        parent.hasPackaging('pom')
+        parent.publish()
+
+        def child = mavenHttpRepo.module("org", "child", "1.0")
+        child.parent("org", "parent", "1.0-SNAPSHOT")
+        child.publish()
+
+        buildFile << """
+repositories {
+    maven { url '${mavenHttpRepo.uri}' }
+}
+configurations { compile }
+dependencies { compile 'org:child:1.0' }
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when:
+        child.pom.expectGet()
+        parent.metaData.expectGet()
+        parent.pom.expectGet()
+
+        // Will always check for a default artifact with a module with 'pom' packaging
+        // TODO - should not make this request
+        parent.artifact.expectHeadMissing()
+
+        child.artifact.expectGet()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('child-1.0.jar')
+    }
+
+    def "looks for parent pom in different repository"() {
+        given:
+        server.start()
+        def repo1 = mavenHttpRepo("repo1")
+        def repo2 = mavenHttpRepo("repo2")
+
+        def parentInRepo1 = repo1.module("org", "parent")
+
+        def parentInRepo2 = repo2.module("org", "parent")
+        parentInRepo2.hasPackaging('pom')
+        parentInRepo2.publish()
+
+        def child = repo1.module("org", "child")
+        child.parent("org", "parent", "1.0")
+        child.publish()
+
+        buildFile << """
+repositories {
+    maven { url '${repo1.uri}' }
+    maven { url '${repo2.uri}' }
+}
+configurations { compile }
+dependencies { compile 'org:child:1.0' }
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when:
+        child.pom.expectGet()
+        child.artifact.expectGet()
+
+        parentInRepo1.pom.expectGetMissing()
+        parentInRepo1.artifact.expectHeadMissing()
+
+        parentInRepo2.pom.expectGet()
+         // TODO - should not make this request
+        parentInRepo2.artifact.expectHeadMissing()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('child-1.0.jar')
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomPackagingResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomPackagingResolveIntegrationTest.groovy
new file mode 100644
index 0000000..18cabfa
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomPackagingResolveIntegrationTest.groovy
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.test.fixtures.maven.MavenFileModule
+import spock.lang.FailsWith
+import spock.lang.Issue
+
+class MavenPomPackagingResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    MavenFileModule projectA
+
+    public setup() {
+        server.start()
+
+        projectA = mavenRepo().module('group', 'projectA')
+    }
+
+    private void buildWithDependencies(def dependencies) {
+        buildFile << """
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+    maven { url 'http://localhost:${server.port}/repo2' }
+}
+configurations { compile }
+dependencies {
+    $dependencies
+}
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+    }
+
+    def "looks for jar artifact for pom with packaging of type 'pom' in the same repository only"() {
+        when:
+        buildWithDependencies("compile 'group:projectA:1.0'")
+        publishWithPackaging('pom')
+
+        and:
+        // First attempts to resolve in repo1
+        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0.pom')
+        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.jar')
+
+        server.expectGet('/repo2/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectHead('/repo2/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGet('/repo2/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.0.jar')
+        def snapshot = file('libs/projectA-1.0.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        and:
+        run 'retrieve'
+
+        then: // Uses cached artifacts
+        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
+    }
+
+    def "will use jar artifact for pom with packaging that maps to jar"() {
+        when:
+        buildWithDependencies("compile 'group:projectA:1.0'")
+        publishWithPackaging(packaging)
+
+        and:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+
+        then:
+        succeeds 'retrieve'
+
+        and:
+        file('libs').assertHasDescendants('projectA-1.0.jar')
+        file('libs/projectA-1.0.jar').assertIsCopyOf(projectA.artifactFile)
+
+        where:
+        packaging << ['', 'jar', 'eclipse-plugin', 'bundle']
+    }
+
+
+    @Issue('GRADLE-2188')
+    def "will use jar artifact for pom with packaging 'orbit'"() {
+        when:
+        buildWithDependencies("compile 'group:projectA:1.0'")
+        publishWithPackaging('orbit')
+
+        and:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.orbit')
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+
+        then:
+        succeeds 'retrieve'
+
+        and:
+        file('libs').assertHasDescendants('projectA-1.0.jar')
+        file('libs/projectA-1.0.jar').assertIsCopyOf(projectA.artifactFile)
+    }
+
+    @Issue('GRADLE-2188')
+    def "where 'module.custom' exists, will use it as main artifact for pom with packaging 'custom' and emit deprecation warning"() {
+        when:
+        buildWithDependencies("compile 'group:projectA:1.0'")
+        publishWithPackaging('custom', 'custom')
+
+        and:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectHead('/repo1/group/projectA/1.0/projectA-1.0.custom', projectA.artifactFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.custom', projectA.artifactFile)
+
+        and:
+        executer.withDeprecationChecksDisabled()
+
+        then:
+        succeeds 'retrieve'
+
+        and:
+        file('libs').assertHasDescendants('projectA-1.0.custom')
+        file('libs/projectA-1.0.custom').assertIsCopyOf(projectA.artifactFile)
+
+        and:
+        result.output.contains("Relying on packaging to define the extension of the main artifact has been deprecated")
+    }
+
+    def "fails and reports type-based location if neither packaging-based or type-based artifact can be located"() {
+        when:
+        buildWithDependencies("compile 'group:projectA:1.0'")
+        publishWithPackaging('custom')
+
+        and:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.custom')
+        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0.jar')
+
+        then:
+        fails 'retrieve'
+
+        and:
+        result.error.contains("Artifact 'group:projectA:1.0 at jar' not found.")
+    }
+
+    def "will use non-jar dependency type to determine jar artifact location"() {
+        when:
+        buildWithDependencies("""
+compile('group:projectA:1.0') {
+    artifact {
+        name = 'projectA'
+        type = 'zip'
+    }
+}
+""")
+        publishWithPackaging('custom')
+
+        and:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+
+        // TODO:GRADLE-2188 This call should not be required, since "type='zip'" on the dependency alleviates the need to check for the packaging artifact
+        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.custom')
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.zip', projectA.artifactFile)
+
+        then:
+        succeeds 'retrieve'
+
+        and:
+        file('libs').assertHasDescendants('projectA-1.0.zip')
+        file('libs/projectA-1.0.zip').assertIsCopyOf(projectA.artifactFile)
+    }
+
+    def "will use non-jar maven dependency type to determine artifact location"() {
+        when:
+        buildWithDependencies("""
+compile 'group:mavenProject:1.0'
+""")
+        def mavenProject = mavenRepo().module('group', 'mavenProject', '1.0').hasType('pom').dependsOn('group', 'projectA', '1.0', 'zip').publish()
+        publishWithPackaging('custom', 'zip')
+
+        and:
+        server.expectGet('/repo1/group/mavenProject/1.0/mavenProject-1.0.pom', mavenProject.pomFile)
+        server.expectHeadMissing('/repo1/group/mavenProject/1.0/mavenProject-1.0.jar')
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+
+        // TODO:GRADLE-2188 This call should not be required, since "type='zip'" on the dependency alleviates the need to check for the packaging artifact
+        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.custom')
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.zip', projectA.artifactFile)
+
+        then:
+        succeeds 'retrieve'
+
+        and:
+        file('libs').assertHasDescendants('projectA-1.0.zip')
+        file('libs/projectA-1.0.zip').assertIsCopyOf(projectA.artifactFile)
+    }
+
+    @FailsWith(value = AssertionError, reason = "Pending better fix for GRADLE-2188")
+    def "does not emit deprecation warning if dependency type is used to locate artifact, even if custom packaging matches file extension"() {
+        when:
+        buildWithDependencies("""
+compile('group:projectA:1.0') {
+    artifact {
+        name = 'projectA'
+        type = 'zip'
+    }
+}
+""")
+        publishWithPackaging('zip')
+
+        and:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.zip', projectA.artifactFile)
+
+        then:
+        succeeds 'retrieve'
+
+        and:
+        file('libs').assertHasDescendants('projectA-1.0.zip')
+        file('libs/projectA-1.0.zip').assertIsCopyOf(projectA.artifactFile)
+
+        and: "Stop the http server here to allow failure to be declared (otherwise occurs in tearDown) - remove this when the test is fixed"
+        server.stop()
+    }
+
+    private def publishWithPackaging(String packaging, String type = 'jar') {
+        projectA.packaging = packaging
+        projectA.type = type
+        projectA.publish()
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotResolveIntegrationTest.groovy
new file mode 100644
index 0000000..6784b20
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotResolveIntegrationTest.groovy
@@ -0,0 +1,546 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.test.fixtures.maven.MavenHttpModule
+
+class MavenSnapshotResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "can find and cache snapshots in multiple Maven HTTP repositories"() {
+        server.start()
+        def repo1 = mavenHttpRepo("repo1")
+        def repo2 = mavenHttpRepo("repo2")
+
+        given:
+        buildFile << """
+repositories {
+    maven { url "${repo1.uri}" }
+    maven { url "${repo2.uri}" }
+}
+
+configurations { compile }
+
+dependencies {
+    compile "org.gradle.integtests.resolve:projectA:1.0-SNAPSHOT"
+    compile "org.gradle.integtests.resolve:projectB:1.0-SNAPSHOT"
+    compile "org.gradle.integtests.resolve:nonunique:1.0-SNAPSHOT"
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        and: "snapshot modules are published"
+        def repo1ProjectA = repo1.module("org.gradle.integtests.resolve", "projectA", "1.0-SNAPSHOT").publish()
+        def repo1ProjectB = repo1.module("org.gradle.integtests.resolve", "projectB", "1.0-SNAPSHOT")
+        def repo2ProjectB = repo2.module("org.gradle.integtests.resolve", "projectB", "1.0-SNAPSHOT").publish()
+        def repo1NonUnique = repo1.module("org.gradle.integtests.resolve", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots()
+        def repo2NonUnique = repo2.module("org.gradle.integtests.resolve", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
+
+        when: "Server provides projectA from repo1"
+        expectModuleServed(repo1ProjectA)
+
+        and: "Server provides projectB from repo2"
+        expectModuleMissing(repo1ProjectB)
+        expectModuleServed(repo2ProjectB)
+
+        and: "Server provides nonunique snapshot from repo2"
+        expectModuleMissing(repo1NonUnique)
+        expectModuleServed(repo2NonUnique)
+
+        and: "We resolve dependencies"
+        run 'retrieve'
+
+        then: "Snapshots are downloaded"
+        file('libs').assertHasDescendants('projectA-1.0-SNAPSHOT.jar', 'projectB-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
+        def snapshotA = file('libs/projectA-1.0-SNAPSHOT.jar').snapshot()
+        def snapshotNonUnique = file('libs/nonunique-1.0-SNAPSHOT.jar').snapshot()
+
+        when: "We resolve with snapshots cached: no server requests"
+        server.resetExpectations()
+        def result = run('retrieve')
+
+        then: "Everything is up to date"
+        result.assertTaskSkipped(':retrieve')
+        file('libs/projectA-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotA);
+        file('libs/nonunique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotNonUnique);
+    }
+
+    def "can find and cache snapshots in Maven HTTP repository with additional artifact urls"() {
+        server.start()
+        def repo1 = mavenHttpRepo("repo1")
+        def repo2 = mavenHttpRepo("repo2")
+
+        given:
+        buildFile << """
+repositories {
+    maven {
+        url "${repo1.uri}"
+        artifactUrls "${repo2.uri}"
+    }
+}
+
+configurations { compile }
+
+dependencies {
+    compile "org.gradle.integtests.resolve:projectA:1.0-SNAPSHOT"
+    compile "org.gradle.integtests.resolve:projectB:1.0-SNAPSHOT"
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        and: "snapshot modules are published"
+        def projectA = repo1.module("org.gradle.integtests.resolve", "projectA", "1.0-SNAPSHOT").publish()
+        def repo1ProjectB = repo1.module("org.gradle.integtests.resolve", "projectB", "1.0-SNAPSHOT").publish()
+        def repo2ProjectB = repo2.module("org.gradle.integtests.resolve", "projectB", "1.0-SNAPSHOT").publish()
+
+        when: "Server provides projectA from repo1"
+        expectModuleServed(projectA)
+
+        and: "Server provides projectB with artifact in repo2"
+        repo1ProjectB.metaData.expectGet()
+        repo1ProjectB.pom.expectGet()
+        repo1ProjectB.artifact.expectGetMissing()
+        repo2ProjectB.artifact.expectGet()
+
+        and: "We resolve dependencies"
+        run 'retrieve'
+
+        then: "Snapshots are downloaded"
+        file('libs').assertHasDescendants('projectA-1.0-SNAPSHOT.jar', 'projectB-1.0-SNAPSHOT.jar')
+        def snapshotA = file('libs/projectA-1.0-SNAPSHOT.jar').snapshot()
+        def snapshotB = file('libs/projectB-1.0-SNAPSHOT.jar').snapshot()
+
+        when: "We resolve with snapshots cached: no server requests"
+        server.resetExpectations()
+        def result = run('retrieve')
+
+        then: "Everything is up to date"
+        result.assertTaskSkipped(':retrieve')
+        file('libs/projectA-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotA);
+        file('libs/projectB-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotB);
+    }
+
+    def "will detect changed snapshot artifacts when pom has not changed"() {
+        server.start()
+
+        buildFile << """
+repositories {
+    maven { url "${mavenHttpRepo.uri}" }
+}
+
+configurations { compile }
+configurations.compile.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+
+dependencies { 
+    compile "org.gradle.integtests.resolve:unique:1.0-SNAPSHOT"
+    compile "org.gradle.integtests.resolve:nonunique:1.0-SNAPSHOT"
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when: "snapshot modules are published"
+        def uniqueVersionModule = mavenHttpRepo.module("org.gradle.integtests.resolve", "unique", "1.0-SNAPSHOT").publish()
+        def nonUniqueVersionModule = mavenHttpRepo.module("org.gradle.integtests.resolve", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
+
+        and: "Server handles requests"
+        expectModuleServed(uniqueVersionModule)
+        expectModuleServed(nonUniqueVersionModule)
+
+        and: "We resolve dependencies"
+        run 'retrieve'
+
+        then: "Snapshots are downloaded"
+        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
+        def uniqueJarSnapshot = file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).snapshot()
+        def nonUniqueJarSnapshot = file('libs/nonunique-1.0-SNAPSHOT.jar').assertIsCopyOf(nonUniqueVersionModule.artifactFile).snapshot()
+        server.resetExpectations()
+
+        when: "Change the snapshot artifacts directly: do not change the pom"
+        uniqueVersionModule.artifactFile << 'more content'
+        uniqueVersionModule.backingModule.sha1File(uniqueVersionModule.artifactFile)
+        nonUniqueVersionModule.artifactFile << 'more content'
+        nonUniqueVersionModule.backingModule.sha1File(nonUniqueVersionModule.artifactFile)
+
+        and: "No server requests"
+        expectChangedArtifactServed(uniqueVersionModule)
+        expectChangedArtifactServed(nonUniqueVersionModule)
+
+        and: "Resolve dependencies again"
+        run 'retrieve'
+
+        then:
+        file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).assertHasChangedSince(uniqueJarSnapshot)
+        file('libs/nonunique-1.0-SNAPSHOT.jar').assertIsCopyOf(nonUniqueVersionModule.artifactFile).assertHasChangedSince(nonUniqueJarSnapshot)
+    }
+
+    def "uses cached snapshots from a Maven HTTP repository until the snapshot timeout is reached"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    maven { url "${mavenHttpRepo.uri}" }
+}
+
+configurations { compile }
+
+if (project.hasProperty('noTimeout')) {
+    configurations.all {
+        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+    }
+}
+
+dependencies {
+    compile "org.gradle.integtests.resolve:unique:1.0-SNAPSHOT"
+    compile "org.gradle.integtests.resolve:nonunique:1.0-SNAPSHOT"
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when: "snapshot modules are published"
+        def uniqueVersionModule = mavenHttpRepo.module("org.gradle.integtests.resolve", "unique", "1.0-SNAPSHOT").publish()
+        def nonUniqueVersionModule = mavenHttpRepo.module("org.gradle.integtests.resolve", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
+
+        and: "Server handles requests"
+        expectModuleServed(uniqueVersionModule)
+        expectModuleServed(nonUniqueVersionModule)
+
+        and: "We resolve dependencies"
+        run 'retrieve'
+
+        then: "Snapshots are downloaded"
+        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
+        def uniqueJarSnapshot = file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).snapshot()
+        def nonUniqueJarSnapshot = file('libs/nonunique-1.0-SNAPSHOT.jar').assertIsCopyOf(nonUniqueVersionModule.artifactFile).snapshot()
+
+        when: "Republish the snapshots"
+        uniqueVersionModule.publishWithChangedContent()
+        nonUniqueVersionModule.publishWithChangedContent()
+
+        and: "No server requests"
+        server.resetExpectations()
+
+        and: "Resolve dependencies again, with cached versions"
+        run 'retrieve'
+
+        then:
+        file('libs/unique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(uniqueJarSnapshot)
+        file('libs/nonunique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(nonUniqueJarSnapshot)
+
+        when: "Server handles requests"
+        expectChangedModuleServed(uniqueVersionModule)
+        expectChangedModuleServed(nonUniqueVersionModule)
+
+        and: "Resolve dependencies with cache expired"
+        executer.withArguments("-PnoTimeout")
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
+        file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).assertHasChangedSince(uniqueJarSnapshot)
+        file('libs/nonunique-1.0-SNAPSHOT.jar').assertIsCopyOf(nonUniqueVersionModule.artifactFile).assertHasChangedSince(nonUniqueJarSnapshot);
+    }
+
+    def "does not download snapshot artifacts after expiry when snapshot has not changed"() {
+        server.start()
+
+        buildFile << """
+repositories {
+    maven { url "${mavenHttpRepo.uri}" }
+}
+
+configurations { compile }
+
+configurations.all {
+    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+
+dependencies {
+    compile "org.gradle.integtests.resolve:testproject:1.0-SNAPSHOT"
+}
+
+task retrieve(type: Sync) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        when: "Publish the first snapshot"
+        def module = mavenHttpRepo.module("org.gradle.integtests.resolve", "testproject", "1.0-SNAPSHOT").publish()
+
+        and: "Server handles requests"
+        expectModuleServed(module)
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
+        def snapshot = file('build/testproject-1.0-SNAPSHOT.jar').assertIsCopyOf(module.artifactFile).snapshot()
+
+        when: "Server handles requests"
+        server.resetExpectations()
+        expectChangedProbe(module)
+
+        // Retrieve again with zero timeout should check for updated snapshot
+        and:
+        def result = run 'retrieve'
+
+        then:
+        result.assertTaskSkipped(':retrieve')
+        file('build/testproject-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshot);
+    }
+
+    def "does not download snapshot artifacts more than once per build"() {
+        server.start()
+        given:
+        def module = mavenHttpRepo.module("org.gradle.integtests.resolve", "testproject", "1.0-SNAPSHOT").publish()
+
+        and:
+        settingsFile << "include 'a', 'b'"
+        buildFile << """
+allprojects {
+    repositories {
+        maven { url "${mavenHttpRepo.uri}" }
+    }
+
+    configurations { compile }
+
+    configurations.all {
+        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+    }
+
+    dependencies {
+        compile "org.gradle.integtests.resolve:testproject:1.0-SNAPSHOT"
+    }
+
+    task retrieve(type: Sync) {
+        into 'build'
+        from configurations.compile
+    }
+}
+"""
+        when: "Module is requested once"
+        expectModuleServed(module)
+
+        then:
+        run 'retrieve'
+
+        and:
+        file('build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
+        file('a/build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
+        file('b/build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
+    }
+
+    def "can update snapshot artifact during build even if it is locked earlier in build"() {
+        server.start()
+        given:
+        def module = mavenHttpRepo("/repo", maven("repo1")).module("org.gradle.integtests.resolve", "testproject", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
+        def module2 = mavenHttpRepo("/repo", maven("repo2")).module("org.gradle.integtests.resolve", "testproject", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
+        module2.pomFile << '    ' // ensure it's a different length to the first one
+        module2.backingModule.sha1File(module2.pomFile)
+        module2.artifactFile << module2.artifactFile.bytes // ensure it's a different length to the first one
+        module2.backingModule.sha1File(module2.artifactFile)
+        and:
+        settingsFile << "include 'first', 'second'"
+        buildFile << """
+def fileLocks = [:]
+subprojects {
+    repositories {
+        maven { url "http://localhost:${server.port}/repo" }
+    }
+
+    configurations { compile }
+
+    configurations.all {
+        resolutionStrategy.resolutionRules.eachArtifact({ artifact ->
+            artifact.refresh()
+        } as Action)
+    }
+
+    dependencies {
+        compile "org.gradle.integtests.resolve:testproject:1.0-SNAPSHOT"
+    }
+
+    task lock << {
+        configurations.compile.each { file ->
+            println "locking " + file
+            def lockFile = new RandomAccessFile(file.canonicalPath, 'r')
+            fileLocks[file] = lockFile
+        }
+    }
+
+    task retrieve(type: Sync) {
+        into 'build'
+        from configurations.compile
+    }
+    retrieve.dependsOn 'lock'
+}
+project('second') {
+    lock.dependsOn ':first:lock'
+    retrieve.dependsOn ':first:retrieve'
+
+    task cleanup << {
+        fileLocks.each { key, value ->
+            println "unlocking " + key
+            value.close()
+        }
+    }
+    cleanup.dependsOn 'retrieve'
+}
+"""
+        when: "Module is requested once"
+        module.metaData.expectGet()
+        module.pom.expectGet()
+        module.artifact.expectGet()
+
+        module2.artifact.expectHead()
+        module2.artifact.sha1.expectGet()
+        module2.artifact.expectGet()
+
+        then:
+        run 'cleanup'
+
+        and:
+        file('first/build/testproject-1.0-SNAPSHOT.jar').assertIsCopyOf(module.artifactFile)
+        file('second/build/testproject-1.0-SNAPSHOT.jar').assertIsCopyOf(module2.artifactFile)
+    }
+
+    def "avoid redownload unchanged artifact when no checksum available"() {
+        server.start()
+
+        given:
+        buildFile << """
+            repositories {
+                maven { url "${mavenHttpRepo.uri}" }
+            }
+
+            configurations { compile }
+
+            configurations.all {
+                resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+            }
+
+            dependencies {
+                compile group: "group", name: "projectA", version: "1.1-SNAPSHOT"
+            }
+
+            task retrieve(type: Copy) {
+                into 'build'
+                from configurations.compile
+            }
+        """
+
+        and:
+        def module = mavenHttpRepo.module("group", "projectA", "1.1-SNAPSHOT").withNonUniqueSnapshots().publish()
+        // Set the last modified to something that's not going to be anything “else”.
+        // There are lots of dates floating around in a resolution and we want to make
+        // sure we use this.
+        module.artifactFile.setLastModified(2000)
+        module.pom.file.setLastModified(6000)
+        def artifact = module.artifact
+
+        when:
+        expectModuleServed(module)
+
+        run "retrieve"
+
+        then:
+        def downloadedJarFile = file("build/projectA-1.1-SNAPSHOT.jar")
+        downloadedJarFile.assertIsCopyOf(module.artifactFile)
+        def initialDownloadJarFileSnapshot = downloadedJarFile.snapshot()
+
+        when:
+        server.resetExpectations()
+        expectChangedProbe(module)
+
+        run "retrieve"
+
+        then:
+        downloadedJarFile.assertHasNotChangedSince(initialDownloadJarFileSnapshot)
+
+        when:
+        module.publishWithChangedContent()
+        server.resetExpectations()
+        module.metaData.expectGet()
+        module.pom.expectHead()
+        module.pom.sha1.expectGetMissing()
+        module.pom.expectGet()
+        artifact.expectHead()
+        artifact.sha1.expectGetMissing()
+        artifact.expectGet()
+
+        run "retrieve"
+
+        then:
+        downloadedJarFile.assertHasChangedSince(initialDownloadJarFileSnapshot)
+        downloadedJarFile.assertIsCopyOf(module.artifactFile)
+    }
+
+    private expectModuleServed(MavenHttpModule module) {
+        module.metaData.expectGet()
+        module.pom.expectGet()
+        module.artifact.expectGet()
+    }
+
+    private expectChangedModuleServed(MavenHttpModule module) {
+        module.metaData.expectGet()
+        module.pom.expectHead()
+        module.pom.sha1.expectGet()
+        module.pom.expectGet()
+        def artifact = module.artifact
+        artifact.expectHead()
+        artifact.sha1.expectGet()
+        artifact.expectGet()
+    }
+
+    private expectChangedArtifactServed(MavenHttpModule module) {
+        module.metaData.expectGet()
+        module.pom.expectHead()
+        def artifact = module.artifact
+        artifact.expectHead()
+        artifact.sha1.expectGet()
+        artifact.expectGet()
+    }
+
+    private expectChangedProbe(MavenHttpModule module) {
+        module.metaData.expectGet()
+        module.pom.expectHead()
+        module.artifact.expectHead()
+    }
+
+    private expectModuleMissing(MavenHttpModule module) {
+        module.metaData.expectGetMissing()
+        module.pom.expectGetMissing()
+        module.artifact.expectHeadMissing()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectA-1.2-ivy.xml b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectA-1.2-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectA-1.2-ivy.xml
rename to subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectA-1.2-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectB-1.5-ivy.xml b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectB-1.5-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectB-1.5-ivy.xml
rename to subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectB-1.5-ivy.xml
diff --git a/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectWithConfigurationHierarchy.gradle b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectWithConfigurationHierarchy.gradle
new file mode 100644
index 0000000..6313ce9
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectWithConfigurationHierarchy.gradle
@@ -0,0 +1,58 @@
+configurations {
+    compile
+    runtime { extendsFrom compile }
+}
+dependencies {
+    repositories {
+        ivy {
+            artifactPattern projectDir.absolutePath + '/[artifact]-[revision].jar'
+            ivyPattern projectDir.absolutePath + '/[module]-[revision]-ivy.xml'
+            ivyPattern projectDir.absolutePath + '/[module]-[revision]-ivy.xml'
+        }
+    }
+    compile group: 'test', name: 'projectA', version: '1.2', configuration: 'api'
+    runtime group: 'test', name: 'projectA', version: '1.2'
+    runtime group: 'test', name: 'projectB', version: '1.5', configuration: 'extraRuntime'
+}
+
+file("projectA-1.2.jar").text = ''
+file("projectB-1.5.jar").text = ''
+file("projectB-api-1.5.jar").text = ''
+file("projectB-extraRuntime-1.5.jar").text = ''
+
+defaultTasks 'listJars'
+
+task listJars << {
+    def compile = configurations.compile
+
+    Set jars = compile.collect { it.name } as Set
+    assert ['projectA-1.2.jar', 'projectB-api-1.5.jar'] as Set == jars
+
+    def projectA = compile.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectA' && it.configuration == 'api' }
+    def root = (projectA.parents as List)[0]
+    def artifacts = projectA.getAllArtifacts(root).collect { it.name } as Set
+    assert ['projectA', 'projectB-api'] as Set == artifacts
+
+    def projectB = projectA.children.find { it.moduleName == 'projectB' && it.configuration == 'compileTime' }
+    artifacts = projectB.getAllArtifacts(projectA).collect { it.name } as Set
+    assert ['projectB-api'] as Set == artifacts
+
+    def runtime = configurations.runtime
+
+    jars = runtime.collect { it.name } as Set
+    assert ['projectA-1.2.jar', 'projectB-api-1.5.jar', 'projectB-1.5.jar', 'projectB-extraRuntime-1.5.jar'] as Set == jars
+
+    projectA = runtime.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectA' && it.configuration == 'api' }
+    root = (projectA.parents as List)[0]
+    artifacts = projectA.getAllArtifacts(root).collect { it.name } as Set
+    assert ['projectA', 'projectB-api'] as Set == artifacts
+
+    projectA = runtime.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectA' && it.configuration == 'default' }
+    root = (projectA.parents as List)[0]
+    artifacts = projectA.getAllArtifacts(root).collect { it.name } as Set
+    assert ['projectA', 'projectB'] as Set == artifacts
+
+    projectB = runtime.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectB' && it.configuration == 'extraRuntime' }
+    artifacts = projectB.getAllArtifacts(root).collect { it.name } as Set
+    assert ['projectB', 'projectB-extraRuntime'] as Set == artifacts
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectA-1.2-ivy.xml b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectA-1.2-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectA-1.2-ivy.xml
rename to subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectA-1.2-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectB-1.5-ivy.xml b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectB-1.5-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectB-1.5-ivy.xml
rename to subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectB-1.5-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectWithCyclesInDependencyGraph.gradle b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectWithCyclesInDependencyGraph.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectWithCyclesInDependencyGraph.gradle
rename to subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectWithCyclesInDependencyGraph.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canNestModules/projectWithNestedModules.gradle b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canNestModules/projectWithNestedModules.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canNestModules/projectWithNestedModules.gradle
rename to subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canNestModules/projectWithNestedModules.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectA-1.2-ivy.xml b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectA-1.2-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectA-1.2-ivy.xml
rename to subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectA-1.2-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectB-1.5-ivy.xml b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectB-1.5-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectB-1.5-ivy.xml
rename to subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectB-1.5-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectWithDynamicVersions.gradle b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectWithDynamicVersions.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectWithDynamicVersions.gradle
rename to subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectWithDynamicVersions.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-1.2-ivy.xml b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-1.2-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-1.2-ivy.xml
rename to subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-1.2-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-2.0-ivy.xml b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-2.0-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-2.0-ivy.xml
rename to subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-2.0-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-1.5-ivy.xml b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-1.5-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-1.5-ivy.xml
rename to subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-1.5-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-2.1.5-ivy.xml b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-2.1.5-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-2.1.5-ivy.xml
rename to subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-2.1.5-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectWithConflicts.gradle b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectWithConflicts.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectWithConflicts.gradle
rename to subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectWithConflicts.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/settings.gradle b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/settings.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/settings.gradle
rename to subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/settings.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/reportsUnknownDependencyError/projectWithUnknownDependency.gradle b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/reportsUnknownDependencyError/projectWithUnknownDependency.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/reportsUnknownDependencyError/projectWithUnknownDependency.gradle
rename to subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/reportsUnknownDependencyError/projectWithUnknownDependency.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest/shared/clientStore b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest/shared/clientStore
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest/shared/clientStore
rename to subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest/shared/clientStore
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest/shared/serverStore b/subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest/shared/serverStore
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest/shared/serverStore
rename to subprojects/core-impl/src/integTest/resources/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest/shared/serverStore
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java
index 1357ebf..5a8e4c9 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java
@@ -17,7 +17,6 @@ package org.gradle.api.internal.artifacts;
 
 import org.apache.ivy.core.module.id.ArtifactRevisionId;
 import org.gradle.StartParameter;
-import org.gradle.api.Transformer;
 import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.dsl.ArtifactHandler;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
@@ -26,9 +25,10 @@ import org.gradle.api.internal.DomainObjectContext;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
 import org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
 import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
-import org.gradle.api.internal.artifacts.dsl.*;
+import org.gradle.api.internal.artifacts.dsl.DefaultArtifactHandler;
+import org.gradle.api.internal.artifacts.dsl.DefaultPublishArtifactFactory;
+import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler;
 import org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler;
 import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
@@ -49,7 +49,7 @@ import org.gradle.api.internal.artifacts.repositories.cachemanager.DownloadingRe
 import org.gradle.api.internal.artifacts.repositories.cachemanager.LocalFileRepositoryCacheManager;
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory;
 import org.gradle.api.internal.externalresource.cached.ByUrlCachedExternalResourceIndex;
-import org.gradle.api.internal.externalresource.ivy.ArtifactAtRepositoryCachedExternalResourceIndex;
+import org.gradle.api.internal.externalresource.ivy.ArtifactAtRepositoryCachedArtifactIndex;
 import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
 import org.gradle.api.internal.externalresource.local.ivy.LocallyAvailableResourceFinderFactory;
 import org.gradle.api.internal.file.FileResolver;
@@ -61,7 +61,6 @@ import org.gradle.api.internal.filestore.ivy.ArtifactRevisionIdFileStore;
 import org.gradle.api.internal.notations.*;
 import org.gradle.api.internal.notations.api.NotationParser;
 import org.gradle.cache.CacheRepository;
-import org.gradle.internal.Factory;
 import org.gradle.internal.SystemProperties;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.DefaultServiceRegistry;
@@ -189,9 +188,8 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
         );
     }
 
-    protected ArtifactAtRepositoryCachedExternalResourceIndex createArtifactAtRepositoryCachedResolutionIndex() {
-        return new ArtifactAtRepositoryCachedExternalResourceIndex(
-                new File(get(ArtifactCacheMetaData.class).getCacheDir(), "artifact-at-repository.bin"),
+    protected ArtifactAtRepositoryCachedArtifactIndex createArtifactAtRepositoryCachedResolutionIndex() {
+        return new ArtifactAtRepositoryCachedArtifactIndex(new File(get(ArtifactCacheMetaData.class).getCacheDir(), "artifact-at-repository.bin"),
                 get(BuildCommencedTimeProvider.class),
                 get(CacheLockingManager.class)
         );
@@ -265,7 +263,6 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
         private final DependencyMetaDataProvider dependencyMetaDataProvider;
         private final ProjectFinder projectFinder;
         private final DomainObjectContext domainObjectContext;
-        private RepositoryFactoryInternal repositoryFactory;
         private DefaultRepositoryHandler repositoryHandler;
         private ConfigurationContainerInternal configurationContainer;
         private DependencyHandler dependencyHandler;
@@ -287,13 +284,6 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
             return repositoryHandler;
         }
 
-        public RepositoryFactoryInternal getRepositoryFactory() {
-            if (repositoryFactory == null) {
-                repositoryFactory = new DefaultRepositoryFactory(getBaseRepositoryFactory());
-            }
-            return repositoryFactory;
-        }
-
         public BaseRepositoryFactory getBaseRepositoryFactory() {
             if (baseRepositoryFactory == null) {
                 Instantiator instantiator = parent.get(Instantiator.class);
@@ -306,12 +296,7 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
                         get(LocallyAvailableResourceFinder.class),
                         get(ProgressLoggerFactory.class),
                         get(LocalFileRepositoryCacheManager.class),
-                        get(DownloadingRepositoryCacheManager.class),
-                        new DefaultArtifactPublisherFactory(new Transformer<ArtifactPublisher, ResolverProvider>() {
-                            public ArtifactPublisher transform(ResolverProvider resolverProvider) {
-                                return createArtifactPublisher(resolverProvider);
-                            }
-                        })
+                        get(DownloadingRepositoryCacheManager.class)
                 );
             }
 
@@ -320,12 +305,12 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
 
         private DefaultRepositoryHandler createRepositoryHandler() {
             Instantiator instantiator = parent.get(Instantiator.class);
-            return instantiator.newInstance(DefaultRepositoryHandler.class, getRepositoryFactory(), instantiator);
+            return instantiator.newInstance(DefaultRepositoryHandler.class, getBaseRepositoryFactory(), instantiator);
         }
 
         public ConfigurationContainerInternal getConfigurationContainer() {
             if (configurationContainer == null) {
-                Instantiator instantiator = parent.get(Instantiator.class);
+                final Instantiator instantiator = parent.get(Instantiator.class);
                 ArtifactDependencyResolver dependencyResolver = createDependencyResolver(getResolveRepositoryHandler());
                 configurationContainer = instantiator.newInstance(DefaultConfigurationContainer.class,
                         dependencyResolver, instantiator, domainObjectContext, parent.get(ListenerManager.class),
@@ -352,12 +337,8 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
             return artifactHandler;
         }
 
-        public Factory<ArtifactPublicationServices> getPublishServicesFactory() {
-            return new Factory<ArtifactPublicationServices>() {
-                public ArtifactPublicationServices create() {
-                    return new DefaultArtifactPublicationServices(DefaultDependencyResolutionServices.this);
-                }
-            };
+        public ArtifactPublicationServices createArtifactPublicationServices() {
+                return new DefaultArtifactPublicationServices(DefaultDependencyResolutionServices.this);
         }
 
         ArtifactDependencyResolver createDependencyResolver(DefaultRepositoryHandler resolverProvider) {
@@ -369,7 +350,7 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
                     get(SettingsConverter.class),
                     get(ModuleResolutionCache.class),
                     get(ModuleDescriptorCache.class),
-                    get(ArtifactAtRepositoryCachedExternalResourceIndex.class),
+                    get(ArtifactAtRepositoryCachedArtifactIndex.class),
                     get(CacheLockingManager.class),
                     startParameterResolutionOverride,
                     get(BuildCommencedTimeProvider.class));
@@ -393,44 +374,41 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
                                             resolver))));
         }
 
-        ArtifactPublisher createArtifactPublisher(ResolverProvider resolverProvider) {
-            PublishModuleDescriptorConverter fileModuleDescriptorConverter = new PublishModuleDescriptorConverter(
-                    get(ResolveModuleDescriptorConverter.class),
-                    new DefaultArtifactsToModuleDescriptorConverter(DefaultArtifactsToModuleDescriptorConverter.IVY_FILE_STRATEGY));
-
-            return new ErrorHandlingArtifactPublisher(
-                    new IvyBackedArtifactPublisher(
-                            resolverProvider,
-                            get(SettingsConverter.class),
-                            get(PublishModuleDescriptorConverter.class),
-                            fileModuleDescriptorConverter,
-                            get(IvyFactory.class),
-                            new DefaultIvyDependencyPublisher(),
-                            new IvyXmlModuleDescriptorWriter()));
+        ArtifactPublisher createArtifactPublisher() {
+            return new IvyBackedArtifactPublisher(
+                    get(SettingsConverter.class),
+                    get(PublishModuleDescriptorConverter.class),
+                    get(IvyFactory.class),
+                    new DefaultIvyDependencyPublisher()
+            );
         }
+
     }
 
     private static class DefaultArtifactPublicationServices implements ArtifactPublicationServices {
         private final DefaultDependencyResolutionServices dependencyResolutionServices;
-        private DefaultRepositoryHandler repositoryHandler;
-        private ArtifactPublisher artifactPublisher;
 
         public DefaultArtifactPublicationServices(DefaultDependencyResolutionServices dependencyResolutionServices) {
             this.dependencyResolutionServices = dependencyResolutionServices;
         }
 
-        public ArtifactPublisher getArtifactPublisher() {
-            if (artifactPublisher == null) {
-                artifactPublisher = dependencyResolutionServices.createArtifactPublisher(getRepositoryHandler());
-            }
-            return artifactPublisher;
+        public DefaultRepositoryHandler createRepositoryHandler() {
+            return dependencyResolutionServices.createRepositoryHandler();
         }
 
-        public DefaultRepositoryHandler getRepositoryHandler() {
-            if (repositoryHandler == null) {
-                repositoryHandler = dependencyResolutionServices.createRepositoryHandler();
-            }
-            return repositoryHandler;
+        public ModuleDescriptorConverter getDescriptorFileModuleConverter() {
+            return new PublishModuleDescriptorConverter(
+                    dependencyResolutionServices.parent.get(ResolveModuleDescriptorConverter.class),
+                    new DefaultArtifactsToModuleDescriptorConverter(DefaultArtifactsToModuleDescriptorConverter.IVY_FILE_STRATEGY));
+        }
+
+        public IvyModuleDescriptorWriter getIvyModuleDescriptorWriter() {
+            return new IvyXmlModuleDescriptorWriter();
+        }
+
+        public ArtifactPublisher createArtifactPublisher() {
+            return dependencyResolutionServices.createArtifactPublisher();
         }
     }
+
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleIdentifier.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleIdentifier.java
new file mode 100755
index 0000000..382bf48
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleIdentifier.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts;
+
+import org.gradle.api.artifacts.ModuleIdentifier;
+
+public class DefaultModuleIdentifier implements ModuleIdentifier {
+    private final String group;
+    private final String name;
+
+    public DefaultModuleIdentifier(String group, String name) {
+        assert group != null : "group cannot be null";
+        assert name != null : "name cannot be null";
+        this.group = group;
+        this.name = name;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s:%s", group, name);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+        DefaultModuleIdentifier other = (DefaultModuleIdentifier) obj;
+        if (!group.equals(other.group)) {
+            return false;
+        }
+        if (!name.equals(other.name)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return group.hashCode() ^ name.hashCode();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifier.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifier.java
new file mode 100755
index 0000000..346f2be
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifier.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts;
+
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.ModuleIdentifier;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+
+public class DefaultModuleVersionIdentifier implements ModuleVersionIdentifier {
+
+    private final DefaultModuleIdentifier id;
+    private final String version;
+
+    public DefaultModuleVersionIdentifier(String group, String name, String version) {
+        assert group != null : "group cannot be null";
+        assert name != null : "name cannot be null";
+        assert version != null : "version cannot be null";
+        this.id = new DefaultModuleIdentifier(group, name);
+        this.version = version;
+    }
+
+    public String getGroup() {
+        return id.getGroup();
+    }
+
+    public String getName() {
+        return id.getName();
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s:%s:%s", id.getGroup(), id.getName(), version);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+        DefaultModuleVersionIdentifier other = (DefaultModuleVersionIdentifier) obj;
+        if (!id.equals(other.id)) {
+            return false;
+        }
+        if (!version.equals(other.version)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode() ^ version.hashCode();
+    }
+
+    public ModuleIdentifier getModule() {
+        return id;
+    }
+
+    public static ModuleVersionIdentifier newId(Module module) {
+        return new DefaultModuleVersionIdentifier(module.getGroup(), module.getName(), module.getVersion());
+    }
+
+    public static ModuleVersionIdentifier newId(String group, String name, String version) {
+        return new DefaultModuleVersionIdentifier(group, name, version);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelector.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelector.java
new file mode 100644
index 0000000..17af643
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelector.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+
+/**
+ * by Szczepan Faber, created at: 11/13/11
+ */
+public class DefaultModuleVersionSelector implements ModuleVersionSelector {
+
+    private String group;
+    private String name;
+    private String version;
+
+    public DefaultModuleVersionSelector(String group, String name, String version) {
+        this.group = group;
+        this.name = name;
+        this.version = version;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public DefaultModuleVersionSelector setGroup(String group) {
+        this.group = group;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public DefaultModuleVersionSelector setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public boolean matchesStrictly(ModuleVersionIdentifier identifier) {
+        return new ModuleVersionSelectorStrictSpec(this).isSatisfiedBy(identifier);
+    }
+
+    public DefaultModuleVersionSelector setVersion(String version) {
+        this.version = version;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s:%s:%s", group, name, version);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultModuleVersionSelector)) {
+            return false;
+        }
+
+        DefaultModuleVersionSelector that = (DefaultModuleVersionSelector) o;
+
+        if (group != null ? !group.equals(that.group) : that.group != null) {
+            return false;
+        }
+        if (name != null ? !name.equals(that.name) : that.name != null) {
+            return false;
+        }
+        if (version != null ? !version.equals(that.version) : that.version != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = group != null ? group.hashCode() : 0;
+        result = 31 * result + (name != null ? name.hashCode() : 0);
+        result = 31 * result + (version != null ? version.hashCode() : 0);
+        return result;
+    }
+
+    public static ModuleVersionSelector newSelector(String group, String name, String version) {
+        return new DefaultModuleVersionSelector(group, name, version);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ModuleVersionIdentifierSerializer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ModuleVersionIdentifierSerializer.java
new file mode 100644
index 0000000..f27db28
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ModuleVersionIdentifierSerializer.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.messaging.serialize.DataStreamBackedSerializer;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class ModuleVersionIdentifierSerializer extends DataStreamBackedSerializer<ModuleVersionIdentifier> {
+    @Override
+    public void write(DataOutput dataOutput, ModuleVersionIdentifier value) throws IOException {
+        dataOutput.writeUTF(value.getGroup());
+        dataOutput.writeUTF(value.getName());
+        dataOutput.writeUTF(value.getVersion());
+    }
+
+    @Override
+    public ModuleVersionIdentifier read(DataInput dataInput) throws IOException {
+        String group = dataInput.readUTF();
+        String module = dataInput.readUTF();
+        String version = dataInput.readUTF();
+        return DefaultModuleVersionIdentifier.newId(group, module, version);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifier.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifier.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifier.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifier.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ResolverResults.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ResolverResults.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ResolverResults.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ResolverResults.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/Configurations.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/Configurations.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/Configurations.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/Configurations.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsProvider.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsProvider.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsProvider.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsProvider.java
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java
new file mode 100644
index 0000000..a75f151
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java
@@ -0,0 +1,569 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.configurations;
+
+import groovy.lang.Closure;
+import org.gradle.api.*;
+import org.gradle.api.artifacts.*;
+import org.gradle.api.artifacts.result.ResolutionResult;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.CompositeDomainObjectSet;
+import org.gradle.api.internal.DefaultDomainObjectSet;
+import org.gradle.api.internal.artifacts.*;
+import org.gradle.api.internal.file.AbstractFileCollection;
+import org.gradle.api.internal.tasks.AbstractTaskDependency;
+import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.listener.ClosureBackedMethodInvocationDispatch;
+import org.gradle.listener.ListenerBroadcast;
+import org.gradle.listener.ListenerManager;
+import org.gradle.util.CollectionUtils;
+import org.gradle.util.ConfigureUtil;
+import org.gradle.util.WrapUtil;
+
+import java.io.File;
+import java.util.*;
+
+import static org.apache.ivy.core.module.descriptor.Configuration.Visibility;
+
+public class DefaultConfiguration extends AbstractFileCollection implements ConfigurationInternal {
+    private final String path;
+    private final String name;
+
+    private Visibility visibility = Visibility.PUBLIC;
+    private boolean transitive = true;
+    private Set<Configuration> extendsFrom = new LinkedHashSet<Configuration>();
+    private String description;
+    private ConfigurationsProvider configurationsProvider;
+    private final ArtifactDependencyResolver dependencyResolver;
+    private final ListenerManager listenerManager;
+    private final DependencyMetaDataProvider metaDataProvider;
+    private final DefaultDependencySet dependencies;
+    private final CompositeDomainObjectSet<Dependency> inheritedDependencies;
+    private final DefaultDependencySet allDependencies;
+    private final DefaultPublishArtifactSet artifacts;
+    private final CompositeDomainObjectSet<PublishArtifact> inheritedArtifacts;
+    private final DefaultPublishArtifactSet allArtifacts;
+    private final ConfigurationResolvableDependencies resolvableDependencies = new ConfigurationResolvableDependencies();
+    private final ListenerBroadcast<DependencyResolutionListener> resolutionListenerBroadcast;
+    private Set<ExcludeRule> excludeRules = new LinkedHashSet<ExcludeRule>();
+
+    // This lock only protects the following fields
+    private final Object lock = new Object();
+    private State state = State.UNRESOLVED;
+    private ResolverResults cachedResolverResults;
+    private final ResolutionStrategyInternal resolutionStrategy;
+
+    public DefaultConfiguration(String path, String name, ConfigurationsProvider configurationsProvider,
+                                ArtifactDependencyResolver dependencyResolver, ListenerManager listenerManager,
+                                DependencyMetaDataProvider metaDataProvider,
+                                ResolutionStrategyInternal resolutionStrategy) {
+        this.path = path;
+        this.name = name;
+        this.configurationsProvider = configurationsProvider;
+        this.dependencyResolver = dependencyResolver;
+        this.listenerManager = listenerManager;
+        this.metaDataProvider = metaDataProvider;
+        this.resolutionStrategy = resolutionStrategy;
+
+        resolutionListenerBroadcast = listenerManager.createAnonymousBroadcaster(DependencyResolutionListener.class);
+
+        DefaultDomainObjectSet<Dependency> ownDependencies = new DefaultDomainObjectSet<Dependency>(Dependency.class);
+        ownDependencies.beforeChange(new VetoContainerChangeAction());
+
+        dependencies = new DefaultDependencySet(String.format("%s dependencies", getDisplayName()), ownDependencies);
+        inheritedDependencies = new CompositeDomainObjectSet<Dependency>(Dependency.class, ownDependencies);
+        allDependencies = new DefaultDependencySet(String.format("%s all dependencies", getDisplayName()), inheritedDependencies);
+
+        DefaultDomainObjectSet<PublishArtifact> ownArtifacts = new DefaultDomainObjectSet<PublishArtifact>(PublishArtifact.class);
+        ownArtifacts.beforeChange(new VetoContainerChangeAction());
+        artifacts = new DefaultPublishArtifactSet(String.format("%s artifacts", getDisplayName()), ownArtifacts);
+        inheritedArtifacts = new CompositeDomainObjectSet<PublishArtifact>(PublishArtifact.class, ownArtifacts);
+        allArtifacts = new DefaultPublishArtifactSet(String.format("%s all artifacts", getDisplayName()), inheritedArtifacts);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public State getState() {
+        synchronized (lock) {
+            return state;
+        }
+    }
+
+    public Module getModule() {
+        return metaDataProvider.getModule();
+    }
+
+    public boolean isVisible() {
+        return visibility == Visibility.PUBLIC;
+    }
+
+    public Configuration setVisible(boolean visible) {
+        throwExceptionIfNotInUnresolvedState();
+        this.visibility = visible ? Visibility.PUBLIC : Visibility.PRIVATE;
+        return this;
+    }
+
+    public Set<Configuration> getExtendsFrom() {
+        return Collections.unmodifiableSet(extendsFrom);
+    }
+
+    public Configuration setExtendsFrom(Set<Configuration> extendsFrom) {
+        throwExceptionIfNotInUnresolvedState();
+        for (Configuration configuration : this.extendsFrom) {
+            inheritedArtifacts.removeCollection(configuration.getAllArtifacts());
+            inheritedDependencies.removeCollection(configuration.getAllDependencies());
+        }
+        this.extendsFrom = new HashSet<Configuration>();
+        for (Configuration configuration : extendsFrom) {
+            extendsFrom(configuration);
+        }
+        return this;
+    }
+
+    public Configuration extendsFrom(Configuration... extendsFrom) {
+        throwExceptionIfNotInUnresolvedState();
+        for (Configuration configuration : extendsFrom) {
+            if (configuration.getHierarchy().contains(this)) {
+                throw new InvalidUserDataException(String.format(
+                        "Cyclic extendsFrom from %s and %s is not allowed. See existing hierarchy: %s", this,
+                        configuration, configuration.getHierarchy()));
+            }
+            this.extendsFrom.add(configuration);
+            inheritedArtifacts.addCollection(configuration.getAllArtifacts());
+            inheritedDependencies.addCollection(configuration.getAllDependencies());
+        }
+        return this;
+    }
+
+    public boolean isTransitive() {
+        return transitive;
+    }
+
+    public Configuration setTransitive(boolean transitive) {
+        throwExceptionIfNotInUnresolvedState();
+        this.transitive = transitive;
+        return this;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public Configuration setDescription(String description) {
+        throwExceptionIfNotInUnresolvedState();
+        this.description = description;
+        return this;
+    }
+
+    public Set<Configuration> getHierarchy() {
+        Set<Configuration> result = WrapUtil.<Configuration>toLinkedSet(this);
+        collectSuperConfigs(this, result);
+        return result;
+    }
+
+    private void collectSuperConfigs(Configuration configuration, Set<Configuration> result) {
+        for (Configuration superConfig : configuration.getExtendsFrom()) {
+            if (result.contains(superConfig)) {
+                result.remove(superConfig);
+            }
+            result.add(superConfig);
+            collectSuperConfigs(superConfig, result);
+        }
+    }
+
+    public Set<Configuration> getAll() {
+        return configurationsProvider.getAll();
+    }
+
+    public Set<File> resolve() {
+        return getFiles();
+    }
+
+    public Set<File> getFiles() {
+        return fileCollection(Specs.SATISFIES_ALL).getFiles();
+    }
+
+    public Set<File> files(Dependency... dependencies) {
+        return fileCollection(dependencies).getFiles();
+    }
+
+    public Set<File> files(Closure dependencySpecClosure) {
+        return fileCollection(dependencySpecClosure).getFiles();
+    }
+
+    public Set<File> files(Spec<? super Dependency> dependencySpec) {
+        return fileCollection(dependencySpec).getFiles();
+    }
+
+    public FileCollection fileCollection(Spec<? super Dependency> dependencySpec) {
+        return new ConfigurationFileCollection(dependencySpec);
+    }
+
+    public FileCollection fileCollection(Closure dependencySpecClosure) {
+        return new ConfigurationFileCollection(dependencySpecClosure);
+    }
+
+    public FileCollection fileCollection(Dependency... dependencies) {
+        return new ConfigurationFileCollection(WrapUtil.toLinkedSet(dependencies));
+    }
+
+    public ResolvedConfiguration getResolvedConfiguration() {
+        resolveNow();
+        return cachedResolverResults.getResolvedConfiguration();
+    }
+
+    private void resolveNow() {
+        synchronized (lock) {
+            if (state == State.UNRESOLVED) {
+                DependencyResolutionListener broadcast = getDependencyResolutionBroadcast();
+                ResolvableDependencies incoming = getIncoming();
+                broadcast.beforeResolve(incoming);
+                cachedResolverResults = dependencyResolver.resolve(this);
+                if (cachedResolverResults.getResolvedConfiguration().hasError()) {
+                    state = State.RESOLVED_WITH_FAILURES;
+                } else {
+                    state = State.RESOLVED;
+                }
+                broadcast.afterResolve(incoming);
+            }
+        }
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return allDependencies.getBuildDependencies();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public TaskDependency getTaskDependencyFromProjectDependency(final boolean useDependedOn, final String taskName) {
+        return new AbstractTaskDependency() {
+            public void resolve(TaskDependencyResolveContext context) {
+                if (useDependedOn) {
+                    addTaskDependenciesFromProjectsIDependOn(taskName, context);
+                } else {
+                    Project thisProject = context.getTask().getProject();
+                    addTaskDependenciesFromProjectsDependingOnMe(thisProject, taskName, context);
+                }
+            }
+
+            private void addTaskDependenciesFromProjectsIDependOn(final String taskName,
+                                                                  final TaskDependencyResolveContext context) {
+                Set<ProjectDependency> projectDependencies = getAllDependencies().withType(ProjectDependency.class);
+                for (ProjectDependency projectDependency : projectDependencies) {
+                    Task nextTask = projectDependency.getDependencyProject().getTasks().findByName(taskName);
+                    if (nextTask != null) {
+                        context.add(nextTask);
+                    }
+                }
+            }
+
+            private void addTaskDependenciesFromProjectsDependingOnMe(final Project thisProject, final String taskName,
+                                                                      final TaskDependencyResolveContext context) {
+                Set<Task> tasksWithName = thisProject.getRootProject().getTasksByName(taskName, true);
+                for (Task nextTask : tasksWithName) {
+                    Configuration configuration = nextTask.getProject().getConfigurations().findByName(getName());
+                    if (configuration != null && doesConfigurationDependOnProject(configuration, thisProject)) {
+                        context.add(nextTask);
+                    }
+                }
+            }
+        };
+    }
+
+    private static boolean doesConfigurationDependOnProject(Configuration configuration, Project project) {
+        Set<ProjectDependency> projectDependencies = configuration.getAllDependencies().withType(ProjectDependency.class);
+        for (ProjectDependency projectDependency : projectDependencies) {
+            if (projectDependency.getDependencyProject().equals(project)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public DependencySet getDependencies() {
+        return dependencies;
+    }
+
+    public DependencySet getAllDependencies() {
+        return allDependencies;
+    }
+
+    public PublishArtifactSet getArtifacts() {
+        return artifacts;
+    }
+
+    public PublishArtifactSet getAllArtifacts() {
+        return allArtifacts;
+    }
+
+    public Set<ExcludeRule> getExcludeRules() {
+        return Collections.unmodifiableSet(excludeRules);
+    }
+
+    public void setExcludeRules(Set<ExcludeRule> excludeRules) {
+        throwExceptionIfNotInUnresolvedState();
+        this.excludeRules = excludeRules;
+    }
+
+    public DefaultConfiguration exclude(Map<String, String> excludeRuleArgs) {
+        throwExceptionIfNotInUnresolvedState();
+        excludeRules.add(new DefaultExcludeRule(excludeRuleArgs.get(ExcludeRule.GROUP_KEY), excludeRuleArgs.get(ExcludeRule.MODULE_KEY)));
+        return this;
+    }
+
+    public String getUploadTaskName() {
+        return Configurations.uploadTaskName(getName());
+    }
+
+    public String getDisplayName() {
+        return String.format("configuration '%s'", path);
+    }
+
+    public ResolvableDependencies getIncoming() {
+        return resolvableDependencies;
+    }
+
+    public Configuration copy() {
+        return createCopy(getDependencies(), false);
+    }
+
+    public Configuration copyRecursive() {
+        return createCopy(getAllDependencies(), true);
+    }
+
+    public Configuration copy(Spec<? super Dependency> dependencySpec) {
+        return createCopy(CollectionUtils.filter(getDependencies(), dependencySpec), false);
+    }
+
+    public Configuration copyRecursive(Spec<? super Dependency> dependencySpec) {
+        return createCopy(CollectionUtils.filter(getAllDependencies(), dependencySpec), true);
+    }
+
+    private DefaultConfiguration createCopy(Set<Dependency> dependencies, boolean recursive) {
+        DetachedConfigurationsProvider configurationsProvider = new DetachedConfigurationsProvider();
+        DefaultConfiguration copiedConfiguration = new DefaultConfiguration(path + "Copy", name + "Copy",
+                configurationsProvider, dependencyResolver, listenerManager, metaDataProvider, resolutionStrategy.copy());
+        configurationsProvider.setTheOnlyConfiguration(copiedConfiguration);
+        // state, cachedResolvedConfiguration, and extendsFrom intentionally not copied - must re-resolve copy
+        // copying extendsFrom could mess up dependencies when copy was re-resolved
+
+        copiedConfiguration.visibility = visibility;
+        copiedConfiguration.transitive = transitive;
+        copiedConfiguration.description = description;
+
+        copiedConfiguration.getArtifacts().addAll(getAllArtifacts());
+
+        // todo An ExcludeRule is a value object but we don't enforce immutability for DefaultExcludeRule as strong as we
+        // should (we expose the Map). We should provide a better API for ExcludeRule (I don't want to use unmodifiable Map).
+        // As soon as DefaultExcludeRule is truly immutable, we don't need to create a new instance of DefaultExcludeRule.
+        Set<Configuration> excludeRuleSources = new LinkedHashSet<Configuration>();
+        excludeRuleSources.add(this);
+        if (recursive) {
+            excludeRuleSources.addAll(getHierarchy());
+        }
+
+        for (Configuration excludeRuleSource : excludeRuleSources) {
+            for (ExcludeRule excludeRule : excludeRuleSource.getExcludeRules()) {
+                copiedConfiguration.excludeRules.add(new DefaultExcludeRule(excludeRule.getGroup(), excludeRule.getModule()));
+            }
+        }
+
+        DomainObjectSet<Dependency> copiedDependencies = copiedConfiguration.getDependencies();
+        for (Dependency dependency : dependencies) {
+            copiedDependencies.add(dependency.copy());
+        }
+        return copiedConfiguration;
+    }
+
+    public Configuration copy(Closure dependencySpec) {
+        return copy(Specs.<Dependency>convertClosureToSpec(dependencySpec));
+    }
+
+    public Configuration copyRecursive(Closure dependencySpec) {
+        return copyRecursive(Specs.<Dependency>convertClosureToSpec(dependencySpec));
+    }
+
+    public DependencyResolutionListener getDependencyResolutionBroadcast() {
+        return resolutionListenerBroadcast.getSource();
+    }
+
+    public ResolutionStrategyInternal getResolutionStrategy() {
+        return resolutionStrategy;
+    }
+
+    public Configuration resolutionStrategy(Closure closure) {
+        ConfigureUtil.configure(closure, resolutionStrategy);
+        return this;
+    }
+
+    private void throwExceptionIfNotInUnresolvedState() {
+        if (getState() != State.UNRESOLVED) {
+            throw new InvalidUserDataException("You can't change a configuration which is not in unresolved state!");
+        }
+    }
+
+    class ConfigurationFileCollection extends AbstractFileCollection {
+        private Spec<? super Dependency> dependencySpec;
+
+        private ConfigurationFileCollection(Spec<? super Dependency> dependencySpec) {
+            this.dependencySpec = dependencySpec;
+        }
+
+        public ConfigurationFileCollection(Closure dependencySpecClosure) {
+            this.dependencySpec = Specs.convertClosureToSpec(dependencySpecClosure);
+        }
+
+        public ConfigurationFileCollection(final Set<Dependency> dependencies) {
+            this.dependencySpec = new Spec<Dependency>() {
+                public boolean isSatisfiedBy(Dependency element) {
+                    return dependencies.contains(element);
+                }
+            };
+        }
+
+        @Override
+        public TaskDependency getBuildDependencies() {
+            return DefaultConfiguration.this.getBuildDependencies();
+        }
+
+        public Spec<? super Dependency> getDependencySpec() {
+            return dependencySpec;
+        }
+
+        public String getDisplayName() {
+            return String.format("%s dependencies", DefaultConfiguration.this);
+        }
+
+        public Set<File> getFiles() {
+            synchronized (lock) {
+                ResolvedConfiguration resolvedConfiguration = getResolvedConfiguration();
+                if (getState() == State.RESOLVED_WITH_FAILURES) {
+                    resolvedConfiguration.rethrowFailure();
+                }
+                return resolvedConfiguration.getFiles(dependencySpec);
+            }
+        }
+    }
+
+    /**
+     * Print a formatted representation of a Configuration
+     */
+    public String dump() {
+        StringBuilder reply = new StringBuilder();
+
+        reply.append("\nConfiguration:");
+        reply.append("  class='" + this.getClass() + "'");
+        reply.append("  name='" + this.getName() + "'");
+        reply.append("  hashcode='" + this.hashCode() + "'");
+
+        reply.append("\nLocal Dependencies:");
+        if (getDependencies().size() > 0) {
+            for (Dependency d : getDependencies()) {
+                reply.append("\n   " + d);
+            }
+        } else {
+            reply.append("\n   none");
+        }
+
+        reply.append("\nLocal Artifacts:");
+        if (getArtifacts().size() > 0) {
+            for (PublishArtifact a : getArtifacts()) {
+                reply.append("\n   " + a);
+            }
+        } else {
+            reply.append("\n   none");
+        }
+
+        reply.append("\nAll Dependencies:");
+        if (getAllDependencies().size() > 0) {
+            for (Dependency d : getAllDependencies()) {
+                reply.append("\n   " + d);
+            }
+        } else {
+            reply.append("\n   none");
+        }
+
+
+        reply.append("\nAll Artifacts:");
+        if (getAllArtifacts().size() > 0) {
+            for (PublishArtifact a : getAllArtifacts()) {
+                reply.append("\n   " + a);
+            }
+        } else {
+            reply.append("\n   none");
+        }
+
+        return reply.toString();
+    }
+
+    private class VetoContainerChangeAction implements Runnable {
+        public void run() {
+            throwExceptionIfNotInUnresolvedState();
+        }
+    }
+
+    private class ConfigurationResolvableDependencies implements ResolvableDependencies {
+        public String getName() {
+            return name;
+        }
+
+        public String getPath() {
+            return path;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("dependencies '%s'", path);
+        }
+
+        public FileCollection getFiles() {
+            return DefaultConfiguration.this.fileCollection(Specs.<Dependency>satisfyAll());
+        }
+
+        public DependencySet getDependencies() {
+            return getAllDependencies();
+        }
+
+        public void beforeResolve(Action<? super ResolvableDependencies> action) {
+            resolutionListenerBroadcast.add("beforeResolve", action);
+        }
+
+        public void beforeResolve(Closure action) {
+            resolutionListenerBroadcast.add(new ClosureBackedMethodInvocationDispatch("beforeResolve", action));
+        }
+
+        public void afterResolve(Action<? super ResolvableDependencies> action) {
+            resolutionListenerBroadcast.add("afterResolve", action);
+        }
+
+        public void afterResolve(Closure action) {
+            resolutionListenerBroadcast.add(new ClosureBackedMethodInvocationDispatch("afterResolve", action));
+        }
+
+        public ResolutionResult getResolutionResult() {
+            //TODO SF unit test
+            DefaultConfiguration.this.resolveNow();
+            return DefaultConfiguration.this.cachedResolverResults.getResolutionResult();
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
new file mode 100644
index 0000000..5e7747d
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.configurations;
+
+import groovy.lang.Closure;
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.UnknownDomainObjectException;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.UnknownConfigurationException;
+import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
+import org.gradle.api.internal.DomainObjectContext;
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
+import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.DefaultResolutionStrategy;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.listener.ListenerManager;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultConfigurationContainer extends AbstractNamedDomainObjectContainer<Configuration> 
+        implements ConfigurationContainerInternal, ConfigurationsProvider {
+    public static final String DETACHED_CONFIGURATION_DEFAULT_NAME = "detachedConfiguration";
+    
+    private final ArtifactDependencyResolver dependencyResolver;
+    private final Instantiator instantiator;
+    private final DomainObjectContext context;
+    private final ListenerManager listenerManager;
+    private final DependencyMetaDataProvider dependencyMetaDataProvider;
+
+    private int detachedConfigurationDefaultNameCounter = 1;
+
+    public DefaultConfigurationContainer(ArtifactDependencyResolver dependencyResolver,
+                                         Instantiator instantiator, DomainObjectContext context, ListenerManager listenerManager,
+                                         DependencyMetaDataProvider dependencyMetaDataProvider) {
+        super(Configuration.class, instantiator, new Configuration.Namer());
+        this.dependencyResolver = dependencyResolver;
+        this.instantiator = instantiator;
+        this.context = context;
+        this.listenerManager = listenerManager;
+        this.dependencyMetaDataProvider = dependencyMetaDataProvider;
+    }
+
+    @Override
+    protected Configuration doCreate(String name) {
+        return instantiator.newInstance(DefaultConfiguration.class, context.absoluteProjectPath(name),
+                name, this, dependencyResolver, listenerManager,
+                dependencyMetaDataProvider, instantiator.newInstance(DefaultResolutionStrategy.class));
+    }
+
+    public Set<Configuration> getAll() {
+        return this;
+    }
+
+    public Configuration add(String name) {
+        return create(name);
+    }
+
+    public Configuration add(String name, Closure closure) {
+        return create(name, closure);
+    }
+
+    @Override
+    public ConfigurationInternal getByName(String name) {
+        return (ConfigurationInternal) super.getByName(name);
+    }
+
+    @Override
+    public String getTypeDisplayName() {
+        return "configuration";
+    }
+
+    @Override
+    protected UnknownDomainObjectException createNotFoundException(String name) {
+        return new UnknownConfigurationException(String.format("Configuration with name '%s' not found.", name));
+    }
+
+    public Configuration detachedConfiguration(Dependency... dependencies) {
+        DetachedConfigurationsProvider detachedConfigurationsProvider = new DetachedConfigurationsProvider();
+        String name = DETACHED_CONFIGURATION_DEFAULT_NAME + detachedConfigurationDefaultNameCounter++;
+        DefaultConfiguration detachedConfiguration = new DefaultConfiguration(
+                name, name, detachedConfigurationsProvider, dependencyResolver,
+                listenerManager, dependencyMetaDataProvider, new DefaultResolutionStrategy());
+        DomainObjectSet<Dependency> detachedDependencies = detachedConfiguration.getDependencies();
+        for (Dependency dependency : dependencies) {
+            detachedDependencies.add(dependency.copy());
+        }
+        detachedConfigurationsProvider.setTheOnlyConfiguration(detachedConfiguration);
+        return detachedConfiguration;
+    }
+    
+    /**
+     * Build a formatted representation of all Configurations in this ConfigurationContainer.
+     * Configuration(s) being toStringed are likely derivations of DefaultConfiguration.
+     */
+    public String dump() {
+        StringBuilder reply = new StringBuilder();
+        
+        reply.append("Configuration of type: " + getTypeDisplayName());
+        Collection<Configuration> configs = getAll();
+        for (Configuration c : configs) {
+            reply.append("\n  " + c.toString());
+        }
+        
+        return reply.toString();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DetachedConfigurationsProvider.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DetachedConfigurationsProvider.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DetachedConfigurationsProvider.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DetachedConfigurationsProvider.java
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ForcedModuleNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ForcedModuleNotationParser.java
new file mode 100644
index 0000000..d9078f7
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ForcedModuleNotationParser.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.dsl;
+
+import org.gradle.api.IllegalDependencyNotation;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
+import org.gradle.api.internal.notations.NotationParserBuilder;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.api.TopLevelNotationParser;
+import org.gradle.api.internal.notations.parsers.MapKey;
+import org.gradle.api.internal.notations.parsers.MapNotationParser;
+import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * by Szczepan Faber, created at: 10/11/11
+ */
+public class ForcedModuleNotationParser implements TopLevelNotationParser, NotationParser<Set<ModuleVersionSelector>> {
+
+    private NotationParser<Set<ModuleVersionSelector>> delegate = new NotationParserBuilder<ModuleVersionSelector>()
+            .resultingType(ModuleVersionSelector.class)
+            .parser(new ForcedModuleStringParser())
+            .parser(new ForcedModuleMapParser())
+            .toFlatteningComposite();
+
+    public Set<ModuleVersionSelector> parseNotation(Object notation) {
+        assert notation != null : "notation cannot be null";
+        return delegate.parseNotation(notation);
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        delegate.describe(candidateFormats);
+    }
+
+    static class ForcedModuleMapParser extends MapNotationParser<ModuleVersionSelector> {
+        @Override
+        public void describe(Collection<String> candidateFormats) {
+            candidateFormats.add("Maps, e.g. [group: 'org.gradle', name:'gradle-core', version: '1.0'].");
+        }
+
+        protected ModuleVersionSelector parseMap(@MapKey("group") String group, @MapKey("name") String name, @MapKey("version") String version) {
+            return selector(group, name, version);
+        }
+    }
+
+    static class ForcedModuleStringParser extends TypedNotationParser<CharSequence, ModuleVersionSelector> {
+
+        public ForcedModuleStringParser() {
+            super(CharSequence.class);
+        }
+
+        @Override
+        public void describe(Collection<String> candidateFormats) {
+            candidateFormats.add("Strings/CharSequences, e.g. 'org.gradle:gradle-core:1.0'.");
+        }
+
+        public ModuleVersionSelector parseType(CharSequence notation) {
+            ParsedModuleStringNotation parsed;
+            try {
+                parsed = new ParsedModuleStringNotation(notation.toString(), null);
+            } catch (IllegalDependencyNotation e) {
+                throw new InvalidUserDataException(
+                    "Invalid format: '" + notation + "'. The Correct notation is a 3-part group:name:version notation,"
+                    + "e.g: 'org.gradle:gradle-core:1.0'");
+            }
+
+            if (parsed.getGroup() == null || parsed.getName() == null || parsed.getVersion() == null) {
+                throw new InvalidUserDataException(
+                    "Invalid format: '" + notation + "'. Group, name and version cannot be empty. Correct example: "
+                    + "'org.gradle:gradle-core:1.0'");
+            }
+            return selector(parsed.getGroup(), parsed.getName(), parsed.getVersion());
+        }
+    }
+
+    static ModuleVersionSelector selector(final String group, final String name, final String version) {
+        return new DefaultModuleVersionSelector(group, name, version);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ParsedModuleStringNotation.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ParsedModuleStringNotation.java
new file mode 100644
index 0000000..cb31a95
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ParsedModuleStringNotation.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.dsl;
+
+import org.gradle.api.IllegalDependencyNotation;
+import org.gradle.util.GUtil;
+
+/**
+ * @author Hans Dockter
+ */
+public class ParsedModuleStringNotation {
+    private String group;
+    private String name;
+    private String version;
+    private String classifier;
+    private String artifactType;
+
+    public ParsedModuleStringNotation(String moduleNotation, String artifactType) {
+        assignValuesFromModuleNotation(moduleNotation);
+        this.artifactType = artifactType;
+    }
+
+    private void assignValuesFromModuleNotation(String moduleNotation) {
+        String[] moduleNotationParts = moduleNotation.split(":");
+        if (moduleNotationParts.length < 2 || moduleNotationParts.length > 4) {
+            throw new IllegalDependencyNotation("The description " + moduleNotation + " is invalid");
+        }
+        group = GUtil.elvis(moduleNotationParts[0], null);
+        name = moduleNotationParts[1];
+        version = moduleNotationParts.length == 2 ? null : moduleNotationParts[2];
+        if (moduleNotationParts.length == 4) {
+            classifier = moduleNotationParts[3];
+        }
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public String getClassifier() {
+        return classifier;
+    }
+
+    public String getArtifactType() {
+        return artifactType;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactResolveResult.java
index 39ce7ad..98852c1 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactResolveResult.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactResolveResult.java
@@ -17,7 +17,6 @@ package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.gradle.api.Nullable;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
-import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
 
 import java.io.File;
 
@@ -33,9 +32,4 @@ public interface ArtifactResolveResult {
      */
     File getFile() throws ArtifactResolveException;
 
-    /**
-     * @throws ArtifactResolveException If the resolution was unsuccessful.
-     */
-    @Nullable
-    ExternalResourceMetaData getExternalResourceMetaData() throws ArtifactResolveException;
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableArtifactResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableArtifactResolveResult.java
index 977f5a1..dd43274 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableArtifactResolveResult.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableArtifactResolveResult.java
@@ -17,17 +17,15 @@
 package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
-import org.gradle.api.Nullable;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
-import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
 
 import java.io.File;
 
 public interface BuildableArtifactResolveResult extends ArtifactResolveResult {
     /**
-     * Marks the module version as resolved, with the given meta-data and artifact resolver.
+     * Marks the module version as resolved, with the given artifact resolver.
      */
-    void resolved(File file, @Nullable ExternalResourceMetaData resourceMetaData);
+    void resolved(File file);
 
     /**
      * Marks the resolve as failed with the given exception.
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableModuleVersionResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableModuleVersionResolveResult.java
index 59d7805..3ffb3ae 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableModuleVersionResolveResult.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableModuleVersionResolveResult.java
@@ -18,22 +18,23 @@ package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
 
 public interface BuildableModuleVersionResolveResult extends ModuleVersionResolveResult {
     /**
      * Marks the module version as resolved, with the given meta-data and artifact resolver.
      */
-    void resolved(ModuleRevisionId moduleRevisionId, ModuleDescriptor descriptor, ArtifactResolver artifactResolver);
+    void resolved(ModuleVersionIdentifier moduleRevisionId, ModuleDescriptor descriptor, ArtifactResolver artifactResolver);
 
     /**
      * Marks the resolve as failed with the given exception.
      */
-    void failed(ModuleVersionResolveException failure);
+    BuildableModuleVersionResolveResult failed(ModuleVersionResolveException failure);
 
     /**
      * Marks the module version as not found.
      */
-    void notFound(ModuleRevisionId moduleRevisionId);
+    void notFound(ModuleVersionIdentifier moduleVersionIdentifier);
 
     /**
      * Replaces the meta-data in the result. Result must already be resolved.
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingManager.java
index de55e64..f731e43 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingManager.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingManager.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal.artifacts.ivyservice;
 import net.jcip.annotations.ThreadSafe;
 import org.gradle.cache.CacheAccess;
 import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.messaging.serialize.Serializer;
 
 import java.io.File;
 
@@ -35,4 +36,14 @@ public interface CacheLockingManager extends ArtifactCacheMetaData, CacheAccess
      * <p>The returned cache may not be used by an action being run from {@link #longRunningOperation(String, org.gradle.internal.Factory)}.
      */
     <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Class<V> valueType);
+
+    /**
+     * Creates a cache implementation that is managed by this locking manager. This method may be used at any time.
+     *
+     * <p>The returned cache may only be used by an action being run from {@link #useCache(String, org.gradle.internal.Factory)}.
+     * In this instance, an exclusive lock will be held on the cache.
+     *
+     * <p>The returned cache may not be used by an action being run from {@link #longRunningOperation(String, org.gradle.internal.Factory)}.
+     */
+    <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactResolveResult.java
index 497bc8e..9e1d486 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactResolveResult.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactResolveResult.java
@@ -17,25 +17,21 @@
 package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
-import org.gradle.api.Nullable;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactNotFoundException;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
-import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
 
 import java.io.File;
 
 public class DefaultBuildableArtifactResolveResult implements BuildableArtifactResolveResult {
     private ArtifactResolveException failure;
     private File file;
-    private ExternalResourceMetaData externalResourceMetaData;
 
     public void failed(ArtifactResolveException failure) {
         this.failure = failure;
     }
 
-    public void resolved(File file, @Nullable ExternalResourceMetaData externalResourceMetaData) {
+    public void resolved(File file) {
         this.file = file;
-        this.externalResourceMetaData = externalResourceMetaData;
     }
 
     public void notFound(Artifact artifact) {
@@ -52,11 +48,6 @@ public class DefaultBuildableArtifactResolveResult implements BuildableArtifactR
         return file;
     }
 
-    public ExternalResourceMetaData getExternalResourceMetaData() throws ArtifactResolveException {
-        assertResolved();
-        return externalResourceMetaData;
-    }
-
     private void assertResolved() {
         assertHasResult();
         if (failure != null) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResult.java
index 681cec4..a7cd877 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResult.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResult.java
@@ -18,31 +18,35 @@ package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
 
 public class DefaultBuildableModuleVersionResolveResult implements BuildableModuleVersionResolveResult {
-    private ModuleRevisionId moduleRevisionId;
+    private ModuleVersionIdentifier moduleVersionIdentifier;
     private ModuleDescriptor moduleDescriptor;
     private ModuleVersionResolveException failure;
     private ArtifactResolver artifactResolver;
 
-    public void failed(ModuleVersionResolveException failure) {
+    public DefaultBuildableModuleVersionResolveResult failed(ModuleVersionResolveException failure) {
         moduleDescriptor = null;
         this.failure = failure;
+        return this;
     }
 
-    public void notFound(ModuleRevisionId moduleRevisionId) {
-        failed(new ModuleVersionNotFoundException(moduleRevisionId));
+
+    public void notFound(ModuleVersionIdentifier moduleVersionIdentifier) {
+        failed(new ModuleVersionNotFoundException(moduleVersionIdentifier));
     }
 
-    public void resolved(ModuleRevisionId moduleRevisionId, ModuleDescriptor descriptor, ArtifactResolver artifactResolver) {
-        this.moduleRevisionId = moduleRevisionId;
+    public void resolved(ModuleVersionIdentifier moduleVersionIdentifier, ModuleDescriptor descriptor, ArtifactResolver artifactResolver) {
+        this.moduleVersionIdentifier = moduleVersionIdentifier;
         this.moduleDescriptor = descriptor;
         this.artifactResolver = artifactResolver;
     }
 
     public void setMetaData(ModuleRevisionId moduleRevisionId, ModuleDescriptor descriptor) {
         assertResolved();
-        this.moduleRevisionId = moduleRevisionId;
+        this.moduleVersionIdentifier = toModuleVersionIdentifier(moduleRevisionId);
         this.moduleDescriptor = descriptor;
     }
 
@@ -51,9 +55,9 @@ public class DefaultBuildableModuleVersionResolveResult implements BuildableModu
         this.artifactResolver = artifactResolver;
     }
 
-    public ModuleRevisionId getId() throws ModuleVersionResolveException {
+    public ModuleVersionIdentifier getId() throws ModuleVersionResolveException {
         assertResolved();
-        return moduleRevisionId;
+        return moduleVersionIdentifier;
     }
 
     public ModuleDescriptor getDescriptor() throws ModuleVersionResolveException {
@@ -71,6 +75,10 @@ public class DefaultBuildableModuleVersionResolveResult implements BuildableModu
         return failure;
     }
 
+    private ModuleVersionIdentifier toModuleVersionIdentifier(ModuleRevisionId moduleRevisionId) {
+        return new DefaultModuleVersionIdentifier(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
+    }
+
     private void assertResolved() {
         assertHasResult();
         if (failure != null) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultCacheLockingManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultCacheLockingManager.java
index ab6ce1c..8928b3e 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultCacheLockingManager.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultCacheLockingManager.java
@@ -15,17 +15,21 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.gradle.internal.Factory;
 import org.gradle.cache.CacheBuilder;
 import org.gradle.cache.CacheRepository;
 import org.gradle.cache.PersistentCache;
 import org.gradle.cache.PersistentIndexedCache;
 import org.gradle.cache.internal.FileLockManager;
+import org.gradle.internal.Factory;
+import org.gradle.messaging.serialize.Serializer;
 
 import java.io.File;
 
 public class DefaultCacheLockingManager implements CacheLockingManager {
-    public static final int CACHE_LAYOUT_VERSION = 15;
+
+    // If you update this, also update DefaultGradleDistribution.getArtifactCacheLayoutVersion() (which is the historical record)
+    public static final int CACHE_LAYOUT_VERSION = 23;
+
     private final PersistentCache cache;
 
     public DefaultCacheLockingManager(CacheRepository cacheRepository) {
@@ -60,4 +64,8 @@ public class DefaultCacheLockingManager implements CacheLockingManager {
     public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Class<V> valueType) {
         return cache.createCache(cacheFile, keyType, valueType);
     }
+
+    public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
+        return cache.createCache(cacheFile, keySerializer, valueSerializer);
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetails.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetails.java
new file mode 100644
index 0000000..0c3333f
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetails.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice;
+
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.internal.artifacts.DependencyResolveDetailsInternal;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector;
+
+/**
+* by Szczepan Faber, created at: 11/29/12
+*/
+public class DefaultDependencyResolveDetails implements DependencyResolveDetailsInternal {
+    private final ModuleVersionSelector requested;
+    private ModuleVersionSelectionReason selectionReason;
+    private ModuleVersionSelector target;
+
+    public DefaultDependencyResolveDetails(ModuleVersionSelector requested) {
+        this.requested = requested;
+        this.target = requested;
+    }
+
+    public ModuleVersionSelector getRequested() {
+        return requested;
+    }
+
+    public void useVersion(String version) {
+        useVersion(version, VersionSelectionReasons.SELECTED_BY_RULE);
+    }
+
+    public void useVersion(String version, ModuleVersionSelectionReason selectionReason) {
+        assert selectionReason != null;
+        if (version == null) {
+            throw new IllegalArgumentException("Configuring the dependency resolve details with 'null' version is not allowed.");
+        }
+        if (!version.equals(target.getVersion())) {
+            target = newSelector(target.getGroup(), target.getName(), version);
+        }
+        this.selectionReason = selectionReason;
+    }
+
+    public ModuleVersionSelectionReason getSelectionReason() {
+        return selectionReason;
+    }
+
+    public ModuleVersionSelector getTarget() {
+        return target;
+    }
+
+    public boolean isUpdated() {
+        return selectionReason != null;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.java
index 7a765ed..6b78236 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.java
@@ -85,13 +85,12 @@ public class DefaultIvyDependencyPublisher implements IvyDependencyPublisher {
 
             boolean successfullyPublished = false;
             try {
-                boolean overwrite = true;
-                resolver.beginPublishTransaction(moduleDescriptor.getModuleRevisionId(), overwrite);
+                resolver.beginPublishTransaction(moduleDescriptor.getModuleRevisionId(), true);
                 // for each declared published artifact in this descriptor, do:
                 for (Map.Entry<Artifact, File> entry : artifactsFiles.entrySet()) {
                     Artifact artifact = entry.getKey();
                     File artifactFile = entry.getValue();
-                    publish(artifact, artifactFile, resolver, overwrite);
+                    publish(artifact, artifactFile, resolver, true);
                 }
                 resolver.commitPublishTransaction();
                 successfullyPublished = true;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactPublisher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactPublisher.java
deleted file mode 100644
index 60b70a6..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactPublisher.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice;
-
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.artifacts.PublishException;
-import org.gradle.api.internal.Transformers;
-import org.gradle.api.internal.XmlTransformer;
-import org.gradle.api.internal.artifacts.ArtifactPublisher;
-
-import java.io.File;
-import java.util.Set;
-import java.util.TreeSet;
-
-import static org.gradle.util.CollectionUtils.collect;
-import static org.gradle.util.CollectionUtils.join;
-
-public class ErrorHandlingArtifactPublisher implements ArtifactPublisher {
-    private final ArtifactPublisher artifactPublisher;
-
-    public ErrorHandlingArtifactPublisher(ArtifactPublisher artifactPublisher) {
-        this.artifactPublisher = artifactPublisher;
-    }
-
-    public void publish(Module module, Set<? extends Configuration> configurations, File descriptorDestination, XmlTransformer descriptorModifier) {
-        try {
-            artifactPublisher.publish(module, configurations, descriptorDestination, descriptorModifier);
-        } catch (Throwable e) {
-            String message = String.format(
-                    "Could not publish configuration%s: [%s]",
-                    configurations.size() > 1 ? "s" : "",
-                    join(", ", collect(configurations, new TreeSet(), Transformers.name(new Configuration.Namer())))
-            );
-            throw new PublishException(message, e);
-        }
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ForcedModuleVersionIdResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ForcedModuleVersionIdResolveResult.java
deleted file mode 100644
index ce4b1f6..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ForcedModuleVersionIdResolveResult.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-
-/**
- * by Szczepan Faber, created at: 8/29/12
- */
-class ForcedModuleVersionIdResolveResult implements ModuleVersionIdResolveResult {
-
-    final ModuleVersionIdResolveResult result;
-
-    public ForcedModuleVersionIdResolveResult(ModuleVersionIdResolveResult result) {
-        this.result = result;
-    }
-
-    public ModuleVersionResolveException getFailure() {
-        return result.getFailure();
-    }
-
-    public ModuleRevisionId getId() throws ModuleVersionResolveException {
-        return result.getId();
-    }
-
-    public ModuleVersionResolveResult resolve() {
-        return result.resolve();
-    }
-
-    public IdSelectionReason getSelectionReason() {
-        return IdSelectionReason.forced;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java
index 3e1ee67..402893c 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java
@@ -16,16 +16,13 @@
 package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.apache.ivy.Ivy;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.Nullable;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.Module;
 import org.gradle.api.artifacts.PublishException;
-import org.gradle.api.internal.XmlTransformer;
 import org.gradle.api.internal.artifacts.ArtifactPublisher;
 import org.gradle.api.internal.artifacts.configurations.Configurations;
-import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
+import org.gradle.util.CollectionUtils;
 
 import java.io.File;
 import java.util.List;
@@ -37,52 +34,33 @@ import java.util.Set;
 public class IvyBackedArtifactPublisher implements ArtifactPublisher {
     private final SettingsConverter settingsConverter;
     private final ModuleDescriptorConverter publishModuleDescriptorConverter;
-    private final ModuleDescriptorConverter fileModuleDescriptorConverter;
     private final IvyFactory ivyFactory;
     private final IvyDependencyPublisher dependencyPublisher;
-    private final ResolverProvider resolverProvider;
-    private final IvyModuleDescriptorWriter ivyModuleDescriptorWriter;
 
-    public IvyBackedArtifactPublisher(ResolverProvider resolverProvider,
-                                      SettingsConverter settingsConverter,
+    public IvyBackedArtifactPublisher(SettingsConverter settingsConverter,
                                       ModuleDescriptorConverter publishModuleDescriptorConverter,
-                                      ModuleDescriptorConverter fileModuleDescriptorConverter,
                                       IvyFactory ivyFactory,
-                                      IvyDependencyPublisher dependencyPublisher,
-                                      IvyModuleDescriptorWriter ivyModuleDescriptorWriter) {
-        this.resolverProvider = resolverProvider;
+                                      IvyDependencyPublisher dependencyPublisher) {
         this.settingsConverter = settingsConverter;
         this.publishModuleDescriptorConverter = publishModuleDescriptorConverter;
-        this.fileModuleDescriptorConverter = fileModuleDescriptorConverter;
         this.ivyFactory = ivyFactory;
         this.dependencyPublisher = dependencyPublisher;
-        this.ivyModuleDescriptorWriter = ivyModuleDescriptorWriter;
     }
 
     private Ivy ivyForPublish(List<DependencyResolver> publishResolvers) {
         return ivyFactory.createIvy(settingsConverter.convertForPublish(publishResolvers));
     }
 
-    public void publish(Module module, Set<? extends Configuration> configurations, File descriptorDestination, @Nullable XmlTransformer descriptorModifier) throws PublishException {
-        List<DependencyResolver> publishResolvers = resolverProvider.getResolvers();
+    public void publish(Iterable<DependencyResolver> dependencyResolvers, Module module, Set<? extends Configuration> configurations, File descriptor) throws PublishException {
+        List<DependencyResolver> publishResolvers = CollectionUtils.toList(dependencyResolvers);
         Ivy ivy = ivyForPublish(publishResolvers);
         Set<String> confs = Configurations.getNames(configurations, false);
-        writeDescriptorFile(descriptorDestination, configurations, module, descriptorModifier);
         dependencyPublisher.publish(
                 confs,
                 publishResolvers,
                 publishModuleDescriptorConverter.convert(configurations, module),
-                descriptorDestination,
+                descriptor,
                 ivy.getEventManager());
     }
 
-    private void writeDescriptorFile(File descriptorDestination, Set<? extends Configuration> configurationsToPublish, Module module, XmlTransformer descriptorModifier) {
-        if (descriptorDestination == null) {
-            return;
-        }
-        assert configurationsToPublish.size() > 0;
-        Set<Configuration> allConfigurations = configurationsToPublish.iterator().next().getAll();
-        ModuleDescriptor moduleDescriptor = fileModuleDescriptorConverter.convert(allConfigurations, module);
-        ivyModuleDescriptorWriter.write(moduleDescriptor, descriptorDestination, descriptorModifier);
-    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.java
deleted file mode 100644
index ab6681a..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.internal.XmlTransformer;
-
-import java.io.File;
-
-public interface IvyModuleDescriptorWriter {
-
-    public void write(ModuleDescriptor md, File output);
-
-    public void write(ModuleDescriptor md, File output, XmlTransformer descriptorModifier);
-
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriter.java
index ace5c83..8c65a4b 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriter.java
@@ -16,7 +16,6 @@
 
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.apache.ivy.Ivy;
 import org.apache.ivy.core.IvyPatternHelper;
 import org.apache.ivy.core.module.descriptor.*;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
@@ -27,19 +26,22 @@ import org.apache.ivy.util.XMLHelper;
 import org.apache.ivy.util.extendable.ExtendableItem;
 import org.gradle.api.Action;
 import org.gradle.api.internal.ErroringAction;
-import org.gradle.api.internal.XmlTransformer;
+import org.gradle.api.internal.xml.XmlTransformer;
 import org.gradle.util.TextUtil;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.Writer;
+import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.Map;
 
 public class IvyXmlModuleDescriptorWriter implements IvyModuleDescriptorWriter {
 
-    private static Action<Writer> createWriterAction(final ModuleDescriptor md) {
+    public static final String IVY_DATE_PATTERN = "yyyyMMddHHmmss";
+
+    private Action<Writer> createWriterAction(final ModuleDescriptor md) {
         return new ErroringAction<Writer>() {
             protected void doExecute(Writer writer) throws IOException {
                 writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
@@ -460,8 +462,11 @@ public class IvyXmlModuleDescriptorWriter implements IvyModuleDescriptorWriter {
         }
         writer.write("\t\tstatus=\"" + XMLHelper.escape(md.getStatus()) + "\"");
         writer.write(TextUtil.getPlatformLineSeparator());
+
+        SimpleDateFormat ivyDateFormat = new SimpleDateFormat(IVY_DATE_PATTERN);
+
         writer.write("\t\tpublication=\""
-                + Ivy.DATE_FORMAT.format(md.getResolvedPublicationDate()) + "\"");
+                + ivyDateFormat.format(md.getResolvedPublicationDate()) + "\"");
         writer.write(TextUtil.getPlatformLineSeparator());
         if (md.isDefault()) {
             writer.write("\t\tdefault=\"true\"");
@@ -561,7 +566,6 @@ public class IvyXmlModuleDescriptorWriter implements IvyModuleDescriptorWriter {
 
     private static String getConfs(ModuleDescriptor md, Artifact artifact) {
         StringBuffer ret = new StringBuffer();
-
         String[] confs = md.getConfigurationsNames();
         for (int i = 0; i < confs.length; i++) {
             if (Arrays.asList(md.getArtifacts(confs[i])).contains(artifact)) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java
deleted file mode 100644
index 9501319..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2007-2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public interface ModuleDescriptorConverter {
-    ModuleDescriptor convert(Set<? extends Configuration> configurations, Module module);
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionIdResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionIdResolveResult.java
index ba8e2a1..ce6a4e0 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionIdResolveResult.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionIdResolveResult.java
@@ -15,8 +15,9 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.Nullable;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
 
 public interface ModuleVersionIdResolveResult {
     /**
@@ -26,24 +27,20 @@ public interface ModuleVersionIdResolveResult {
     ModuleVersionResolveException getFailure();
 
     /**
-     * Returns the id of this module version.
+     * Returns the identifier of this module version.
      *
-     * @throws ModuleVersionResolveException If resolution was unsuccessful and the id is unknown.
+     * @throws ModuleVersionResolveException If id resolution was unsuccessful and the id is unknown.
      */
-    ModuleRevisionId getId() throws ModuleVersionResolveException;
+    ModuleVersionIdentifier getId() throws ModuleVersionResolveException;
 
     /**
      * Resolves the meta-data for this module version, if required. Failures are packaged up in the result.
+     * @throws ModuleVersionResolveException If id resolution was unsuccessful and the id is unknown.
      */
-    ModuleVersionResolveResult resolve();
+    ModuleVersionResolveResult resolve() throws ModuleVersionResolveException;
 
     /**
-     * @return why given id was selected
+     * @return why given id was selected. Should return a value even if the resolve failed.
      */
-    IdSelectionReason getSelectionReason();
-
-    public static enum IdSelectionReason {
-        requested, forced
-        //TODO SF consider changing to an interface with isForced()
-    }
-}
+    ModuleVersionSelectionReason getSelectionReason();
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundException.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundException.java
index 9735431..5d9712c 100755
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundException.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundException.java
@@ -16,13 +16,23 @@
 package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
 
 public class ModuleVersionNotFoundException extends ModuleVersionResolveException {
+    public ModuleVersionNotFoundException(ModuleVersionSelector selector, String messageFormat) {
+        super(selector, messageFormat);
+    }
+
+    public ModuleVersionNotFoundException(ModuleRevisionId id, String messageFormat) {
+        super(id, messageFormat);
+    }
+
     public ModuleVersionNotFoundException(ModuleRevisionId id) {
-        super(String.format("Could not find group:%s, module:%s, version:%s.", id.getOrganisation(), id.getName(), id.getRevision()));
+        super(id, "Could not find %s.");
     }
 
-    public ModuleVersionNotFoundException(String message) {
-        super(message);
+    public ModuleVersionNotFoundException(ModuleVersionIdentifier id) {
+        super(id, "Could not find %s.");
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveException.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveException.java
index ece8d64..b241209 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveException.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveException.java
@@ -16,8 +16,11 @@
 package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.internal.AbstractMultiCauseException;
 import org.gradle.api.internal.Contextual;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
 import org.gradle.internal.UncheckedException;
 
 import java.util.ArrayList;
@@ -28,32 +31,58 @@ import java.util.List;
 @Contextual
 public class ModuleVersionResolveException extends AbstractMultiCauseException {
     private final List<List<ModuleRevisionId>> paths = new ArrayList<List<ModuleRevisionId>>();
+    private final String messageFormat;
+    private final ModuleVersionSelector selector;
 
-    public ModuleVersionResolveException(String message) {
-        super(message);
+    public ModuleVersionResolveException(ModuleVersionSelector selector, String messageFormat) {
+        super(format(messageFormat, selector));
+        this.selector = selector;
+        this.messageFormat = messageFormat;
+    }
+
+    public ModuleVersionResolveException(ModuleRevisionId id, String messageFormat) {
+        this(DefaultModuleVersionSelector.newSelector(id.getOrganisation(), id.getName(), id.getRevision()), messageFormat);
+    }
+
+    public ModuleVersionResolveException(ModuleVersionIdentifier id, String messageFormat) {
+        this(DefaultModuleVersionSelector.newSelector(id.getGroup(), id.getName(), id.getVersion()), messageFormat);
+    }
+
+    public ModuleVersionResolveException(ModuleVersionSelector selector, Throwable cause) {
+        this(selector, "Could not resolve %s.");
+        initCause(cause);
     }
 
     public ModuleVersionResolveException(ModuleRevisionId id, Throwable cause) {
-        super(format(id), cause);
+        this(id, "Could not resolve %s.");
+        initCause(cause);
     }
 
     public ModuleVersionResolveException(ModuleRevisionId id, Iterable<? extends Throwable> causes) {
-        super(format(id), causes);
+        this(id, "Could not resolve %s.");
+        initCauses(causes);
+    }
+
+    /**
+     * Returns the selector that could not be resolved.
+     */
+    public ModuleVersionSelector getSelector() {
+        return selector;
     }
 
-    public ModuleVersionResolveException(String message, Throwable cause) {
-        super(message, cause);
+    private static String format(String messageFormat, ModuleVersionSelector id) {
+        return format(messageFormat, id.getGroup(), id.getName(), id.getVersion());
     }
 
-    private static String format(ModuleRevisionId id) {
-        return String.format("Could not resolve group:%s, module:%s, version:%s.", id.getOrganisation(), id.getName(), id.getRevision());
+    private static String format(String messageFormat, String group, String name, String version) {
+        return String.format(messageFormat, String.format("%s:%s:%s", group, name, version));
     }
 
     /**
      * Creates a copy of this exception, with the given incoming paths.
      */
     public ModuleVersionResolveException withIncomingPaths(Collection<? extends List<ModuleRevisionId>> paths) {
-        ModuleVersionResolveException copy = createCopy(super.getMessage());
+        ModuleVersionResolveException copy = createCopy();
         copy.paths.addAll(paths);
         copy.initCauses(getCauses());
         copy.setStackTrace(getStackTrace());
@@ -80,9 +109,9 @@ public class ModuleVersionResolveException extends AbstractMultiCauseException {
         return String.format("%s:%s:%s", moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
     }
 
-    protected ModuleVersionResolveException createCopy(String message) {
+    protected ModuleVersionResolveException createCopy() {
         try {
-            return getClass().getConstructor(String.class).newInstance(message);
+            return getClass().getConstructor(ModuleVersionSelector.class, String.class).newInstance(selector, messageFormat);
         } catch (Exception e) {
             throw UncheckedException.throwAsUncheckedException(e);
         }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveResult.java
index 5168767..b99ef91 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveResult.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveResult.java
@@ -16,8 +16,8 @@
 package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.Nullable;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
 
 public interface ModuleVersionResolveResult {
     /**
@@ -25,7 +25,7 @@ public interface ModuleVersionResolveResult {
      *
      * @throws ModuleVersionResolveException If resolution was unsuccessful and the id is unknown.
      */
-    ModuleRevisionId getId() throws ModuleVersionResolveException;
+    ModuleVersionIdentifier getId() throws ModuleVersionResolveException;
 
     /**
      * Returns the descriptor for this module version.
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SubstitutedModuleVersionIdResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SubstitutedModuleVersionIdResolveResult.java
new file mode 100644
index 0000000..8971b89
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SubstitutedModuleVersionIdResolveResult.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+
+/**
+ * by Szczepan Faber, created at: 8/29/12
+ */
+class SubstitutedModuleVersionIdResolveResult implements ModuleVersionIdResolveResult {
+
+    final ModuleVersionIdResolveResult result;
+    private final ModuleVersionSelectionReason selectionReason;
+
+    public SubstitutedModuleVersionIdResolveResult(ModuleVersionIdResolveResult result, ModuleVersionSelectionReason selectionReason) {
+        this.result = result;
+        this.selectionReason = selectionReason;
+    }
+
+    public ModuleVersionResolveException getFailure() {
+        return result.getFailure();
+    }
+
+    public ModuleVersionIdentifier getId() throws ModuleVersionResolveException {
+        return result.getId();
+    }
+
+    public ModuleVersionResolveResult resolve() {
+        return result.resolve();
+    }
+
+    public ModuleVersionSelectionReason getSelectionReason() {
+        return selectionReason;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolver.java
index 9765061..730abe7 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolver.java
@@ -18,29 +18,63 @@ package org.gradle.api.internal.artifacts.ivyservice;
 import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.apache.ivy.core.module.id.ModuleId;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.ModuleVersionSelector;
-
-import java.util.HashMap;
-import java.util.Map;
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
+import org.gradle.api.internal.artifacts.DependencyResolveDetailsInternal;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
 
 public class VersionForcingDependencyToModuleResolver implements DependencyToModuleVersionIdResolver {
     private final DependencyToModuleVersionIdResolver resolver;
-    private final Map<ModuleId, ModuleRevisionId> forcedModules = new HashMap<ModuleId, ModuleRevisionId>();
+    private Action<DependencyResolveDetailsInternal> rule;
 
-    public VersionForcingDependencyToModuleResolver(DependencyToModuleVersionIdResolver resolver, Iterable<? extends ModuleVersionSelector> forcedModules) {
+    public VersionForcingDependencyToModuleResolver(DependencyToModuleVersionIdResolver resolver, Action<DependencyResolveDetailsInternal> rule) {
         this.resolver = resolver;
-        for (ModuleVersionSelector forcedModule : forcedModules) {
-            ModuleId moduleId = new ModuleId(forcedModule.getGroup(), forcedModule.getName());
-            this.forcedModules.put(moduleId, new ModuleRevisionId(moduleId, forcedModule.getVersion()));
-        }
+        this.rule = rule;
     }
 
     public ModuleVersionIdResolveResult resolve(DependencyDescriptor dependencyDescriptor) {
-        ModuleRevisionId newRevisionId = forcedModules.get(dependencyDescriptor.getDependencyId());
-        if (newRevisionId != null) {
-            ModuleVersionIdResolveResult result = resolver.resolve(dependencyDescriptor.clone(newRevisionId));
-            return new ForcedModuleVersionIdResolveResult(result);
+        ModuleVersionSelector module = new DefaultModuleVersionSelector(dependencyDescriptor.getDependencyRevisionId().getOrganisation(), dependencyDescriptor.getDependencyRevisionId().getName(), dependencyDescriptor.getDependencyRevisionId().getRevision());
+        DefaultDependencyResolveDetails details = new DefaultDependencyResolveDetails(module);
+        try {
+            rule.execute(details);
+        } catch (Throwable e) {
+            return new FailedDependencyResolveRuleResult(module, e);
+        }
+        if (details.isUpdated()) {
+            ModuleId moduleId = new ModuleId(details.getTarget().getGroup(), details.getTarget().getName());
+            ModuleRevisionId revisionId = new ModuleRevisionId(moduleId, details.getTarget().getVersion());
+            DependencyDescriptor descriptor = dependencyDescriptor.clone(revisionId);
+            ModuleVersionIdResolveResult result = resolver.resolve(descriptor);
+            return new SubstitutedModuleVersionIdResolveResult(result, details.getSelectionReason());
         }
         return resolver.resolve(dependencyDescriptor);
     }
-}
+
+    private class FailedDependencyResolveRuleResult implements ModuleVersionIdResolveResult {
+
+        private final ModuleVersionResolveException failure;
+
+        public FailedDependencyResolveRuleResult(ModuleVersionSelector module, Throwable problem) {
+            this.failure = new ModuleVersionResolveException(module, problem);
+        }
+
+        public ModuleVersionResolveException getFailure() {
+            return failure;
+        }
+
+        public ModuleVersionIdentifier getId() throws ModuleVersionResolveException {
+            throw failure;
+        }
+
+        public ModuleVersionResolveResult resolve() throws ModuleVersionResolveException {
+            throw failure;
+        }
+
+        public ModuleVersionSelectionReason getSelectionReason() {
+            return VersionSelectionReasons.REQUESTED;
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleResolution.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleResolution.java
index 1451b0b..74d26a0 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleResolution.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleResolution.java
@@ -16,34 +16,24 @@
 package org.gradle.api.internal.artifacts.ivyservice.dynamicversions;
 
 import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ResolvedModuleVersion;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.internal.TimeProvider;
 
-import java.io.Serializable;
-
-class DefaultCachedModuleResolution implements ModuleResolutionCache.CachedModuleResolution, Serializable {
+class DefaultCachedModuleResolution implements ModuleResolutionCache.CachedModuleResolution {
     private final ModuleRevisionId requestedVersion;
-    private final ModuleRevisionId resolvedVersion;
+    private final ModuleVersionIdentifier resolvedVersion;
     private final long ageMillis;
 
     public DefaultCachedModuleResolution(ModuleRevisionId requestedVersion, ModuleResolutionCacheEntry entry, TimeProvider timeProvider) {
         this.requestedVersion = requestedVersion;
-        this.resolvedVersion = ModuleRevisionId.decode(entry.encodedRevisionId);
+        this.resolvedVersion = entry.moduleVersionIdentifier;
         ageMillis = timeProvider.getCurrentTime() - entry.createTimestamp;
     }
 
-    public ModuleRevisionId getRequestedVersion() {
-        return requestedVersion;
-    }
-
-    public ModuleRevisionId getResolvedVersion() {
+    public ModuleVersionIdentifier getResolvedVersion() {
         return resolvedVersion;
     }
 
-    public ResolvedModuleVersion getResolvedModule() {
-        return new DefaultResolvedModuleVersion(resolvedVersion);
-    }
-
     public long getAgeMillis() {
         return ageMillis;
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCache.java
index 7f979aa..ed9717f 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCache.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCache.java
@@ -16,19 +16,17 @@
 package org.gradle.api.internal.artifacts.ivyservice.dynamicversions;
 
 import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ResolvedModuleVersion;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
 
 public interface ModuleResolutionCache {
 
-    void cacheModuleResolution(ModuleVersionRepository repository, ModuleRevisionId dynamicVersion, ModuleRevisionId resolvedVersion);
+    void cacheModuleResolution(ModuleVersionRepository repository, ModuleRevisionId dynamicVersion, ModuleVersionIdentifier moduleVersionIdentifier);
 
     CachedModuleResolution getCachedModuleResolution(ModuleVersionRepository repository, ModuleRevisionId dynamicVersion);
 
     interface CachedModuleResolution {
-        ModuleRevisionId getRequestedVersion();
-        ModuleRevisionId getResolvedVersion();
-        ResolvedModuleVersion getResolvedModule();
+        ModuleVersionIdentifier getResolvedVersion();
 
         boolean isDynamicVersion();
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCacheEntry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCacheEntry.java
index 860a8f0..3781c0a 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCacheEntry.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCacheEntry.java
@@ -15,17 +15,14 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.dynamicversions;
 
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.internal.TimeProvider;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
 
-import java.io.Serializable;
-
-class ModuleResolutionCacheEntry implements Serializable {
-    public String encodedRevisionId;
+class ModuleResolutionCacheEntry {
+    public ModuleVersionIdentifier moduleVersionIdentifier;
     public long createTimestamp;
 
-    ModuleResolutionCacheEntry(ModuleRevisionId revisionId, TimeProvider timeProvider) {
-        this.encodedRevisionId = revisionId == null ? null : revisionId.encodeToString();
-        this.createTimestamp = timeProvider.getCurrentTime();
+    ModuleResolutionCacheEntry(ModuleVersionIdentifier moduleVersionIdentifier, long createTimestamp) {
+        this.moduleVersionIdentifier = moduleVersionIdentifier;
+        this.createTimestamp = createTimestamp;
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/SingleFileBackedModuleResolutionCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/SingleFileBackedModuleResolutionCache.java
index ff89af8..5aa0bac 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/SingleFileBackedModuleResolutionCache.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/SingleFileBackedModuleResolutionCache.java
@@ -16,16 +16,18 @@
 package org.gradle.api.internal.artifacts.ivyservice.dynamicversions;
 
 import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ModuleVersionIdentifierSerializer;
 import org.gradle.api.internal.artifacts.ivyservice.ArtifactCacheMetaData;
 import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
 import org.gradle.cache.PersistentIndexedCache;
 import org.gradle.internal.TimeProvider;
+import org.gradle.messaging.serialize.DataStreamBackedSerializer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
-import java.io.Serializable;
+import java.io.*;
 
 public class SingleFileBackedModuleResolutionCache implements ModuleResolutionCache {
     private static final Logger LOGGER = LoggerFactory.getLogger(SingleFileBackedModuleResolutionCache.class);
@@ -50,16 +52,16 @@ public class SingleFileBackedModuleResolutionCache implements ModuleResolutionCa
 
     private PersistentIndexedCache<RevisionKey, ModuleResolutionCacheEntry> initCache() {
         File dynamicRevisionsFile = new File(cacheMetadata.getCacheDir(), "dynamic-revisions.bin");
-        return cacheLockingManager.createCache(dynamicRevisionsFile, RevisionKey.class, ModuleResolutionCacheEntry.class);
+        return cacheLockingManager.createCache(dynamicRevisionsFile, new RevisionKeySerializer(), new ModuleResolutionCacheEntrySerializer());
     }
 
-    public void cacheModuleResolution(ModuleVersionRepository repository, ModuleRevisionId requestedVersion, ModuleRevisionId resolvedVersion) {
-        if (requestedVersion.equals(resolvedVersion)) {
+    public void cacheModuleResolution(ModuleVersionRepository repository, ModuleRevisionId requestedVersion, ModuleVersionIdentifier moduleVersionIdentifier) {
+        if (requestedVersion.equals(moduleVersionIdentifier)) {
             return;
         }
 
-        LOGGER.debug("Caching resolved revision in dynamic revision cache: Will use '{}' for '{}'", resolvedVersion, requestedVersion);
-        getCache().put(createKey(repository, requestedVersion), createEntry(resolvedVersion));
+        LOGGER.debug("Caching resolved revision in dynamic revision cache: Will use '{}' for '{}'", moduleVersionIdentifier, requestedVersion);
+        getCache().put(createKey(repository, requestedVersion), createEntry(moduleVersionIdentifier));
     }
 
     public CachedModuleResolution getCachedModuleResolution(ModuleVersionRepository repository, ModuleRevisionId moduleId) {
@@ -71,20 +73,20 @@ public class SingleFileBackedModuleResolutionCache implements ModuleResolutionCa
     }
 
     private RevisionKey createKey(ModuleVersionRepository repository, ModuleRevisionId revisionId) {
-        return new RevisionKey(repository, revisionId);
+        return new RevisionKey(repository.getId(), revisionId.encodeToString());
     }
 
-    private ModuleResolutionCacheEntry createEntry(ModuleRevisionId revisionId) {
-        return new ModuleResolutionCacheEntry(revisionId, timeProvider);
+    private ModuleResolutionCacheEntry createEntry(ModuleVersionIdentifier moduleVersionIdentifier) {
+        return new ModuleResolutionCacheEntry(moduleVersionIdentifier, timeProvider.getCurrentTime());
     }
 
-    private static class RevisionKey implements Serializable {
-        private final String resolverId;
+    private static class RevisionKey {
+        private final String repositoryId;
         private final String revisionId;
 
-        private RevisionKey(ModuleVersionRepository repository, ModuleRevisionId revision) {
-            this.resolverId = repository.getId();
-            this.revisionId = revision.encodeToString();
+        private RevisionKey(String repositoryId, String revisionId) {
+            this.repositoryId = repositoryId;
+            this.revisionId = revisionId;
         }
 
         @Override
@@ -93,12 +95,44 @@ public class SingleFileBackedModuleResolutionCache implements ModuleResolutionCa
                 return false;
             }
             RevisionKey other = (RevisionKey) o;
-            return resolverId.equals(other.resolverId) && revisionId.equals(other.revisionId);
+            return repositoryId.equals(other.repositoryId) && revisionId.equals(other.revisionId);
         }
 
         @Override
         public int hashCode() {
-            return resolverId.hashCode() ^ revisionId.hashCode();
+            return repositoryId.hashCode() ^ revisionId.hashCode();
+        }
+    }
+
+    private static class RevisionKeySerializer extends DataStreamBackedSerializer<RevisionKey> {
+        @Override
+        public void write(DataOutput dataOutput, RevisionKey value) throws IOException {
+            dataOutput.writeUTF(value.repositoryId);
+            dataOutput.writeUTF(value.revisionId);
+        }
+
+        @Override
+        public RevisionKey read(DataInput dataInput) throws IOException {
+            String resolverId = dataInput.readUTF();
+            String revisionId = dataInput.readUTF();
+            return new RevisionKey(resolverId, revisionId);
+        }
+    }
+
+    private static class ModuleResolutionCacheEntrySerializer extends DataStreamBackedSerializer<ModuleResolutionCacheEntry> {
+        private final ModuleVersionIdentifierSerializer identifierSerializer = new ModuleVersionIdentifierSerializer();
+
+        @Override
+        public void write(DataOutput dataOutput, ModuleResolutionCacheEntry value) throws IOException {
+            identifierSerializer.write(dataOutput, value.moduleVersionIdentifier);
+            dataOutput.writeLong(value.createTimestamp);
+        }
+
+        @Override
+        public ModuleResolutionCacheEntry read(DataInput dataInput) throws IOException {
+            ModuleVersionIdentifier identifier = identifierSerializer.read(dataInput);
+            long createTimestamp = dataInput.readLong();
+            return new ModuleResolutionCacheEntry(identifier, createTimestamp);
         }
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/AbstractDependencyResolverAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/AbstractDependencyResolverAdapter.java
index 553dad2..412db74 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/AbstractDependencyResolverAdapter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/AbstractDependencyResolverAdapter.java
@@ -15,21 +15,10 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.report.ArtifactDownloadReport;
-import org.apache.ivy.core.report.DownloadStatus;
-import org.apache.ivy.core.resolve.ResolveData;
-import org.apache.ivy.core.resolve.ResolvedModuleRevision;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.internal.artifacts.repositories.cachemanager.LocalFileRepositoryCacheManager;
-import org.gradle.internal.UncheckedException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.text.ParseException;
 
 public abstract class AbstractDependencyResolverAdapter implements ModuleVersionRepository {
-    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDependencyResolverAdapter.class);
     private final DependencyResolverIdentifier identifier;
     protected final DependencyResolver resolver;
 
@@ -54,30 +43,4 @@ public abstract class AbstractDependencyResolverAdapter implements ModuleVersion
     public boolean isLocal() {
         return resolver.getRepositoryCacheManager() instanceof LocalFileRepositoryCacheManager;
     }
-
-    protected boolean downloadFailed(ArtifactDownloadReport artifactReport) {
-        // Ivy reports FAILED with MISSING_ARTIFACT message when the artifact doesn't exist.
-        return artifactReport.getDownloadStatus() == DownloadStatus.FAILED
-                && !artifactReport.getDownloadDetails().equals(ArtifactDownloadReport.MISSING_ARTIFACT);
-    }
-
-    public void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
-        ResolveData resolveData = IvyContextualiser.getIvyContext().getResolveData();
-        try {
-            ResolvedModuleRevision revision = resolver.getDependency(dependencyDescriptor, resolveData);
-            if (revision == null) {
-                LOGGER.debug("Performed resolved of module '{}' in repository '{}': not found", dependencyDescriptor.getDependencyRevisionId(), getName());
-                result.missing();
-            } else {
-                LOGGER.debug("Performed resolved of module '{}' in repository '{}': found", dependencyDescriptor.getDependencyRevisionId(), getName());
-                result.resolved(revision.getDescriptor(), isChanging(revision));
-            }
-        } catch (ParseException e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-    }
-
-    private boolean isChanging(ResolvedModuleRevision resolvedModuleRevision) {
-        return new ChangingModuleDetector(resolver).isChangingModule(resolvedModuleRevision.getDescriptor());
-    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactOriginWithMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactOriginWithMetaData.java
deleted file mode 100644
index 0fc8894..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactOriginWithMetaData.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
-
-import org.apache.ivy.core.cache.ArtifactOrigin;
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.plugins.repository.Resource;
-import org.gradle.api.internal.externalresource.ExternalResource;
-import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData;
-import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
-
-public class ArtifactOriginWithMetaData extends ArtifactOrigin {
-
-    private final ExternalResourceMetaData metaData;
-
-    public ArtifactOriginWithMetaData(Artifact artifact, Resource resource) {
-        this(artifact, resource.isLocal(), getMetaData(resource));
-    }
-    public ArtifactOriginWithMetaData(Artifact artifact, boolean isLocal, ExternalResourceMetaData metaData) {
-        super(artifact, isLocal, metaData.getLocation());
-        this.metaData = metaData;
-    }
-
-    public ExternalResourceMetaData getMetaData() {
-        return metaData;
-    }
-    
-    private static ExternalResourceMetaData getMetaData(Resource resource) {
-        if (resource instanceof ExternalResource) {
-            return ((ExternalResource) resource).getMetaData();
-        } else {
-            return new DefaultExternalResourceMetaData(resource.getName());
-        }
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionDescriptor.java
index 4e6811a..d6b7908 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionDescriptor.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionDescriptor.java
@@ -37,9 +37,9 @@ public interface BuildableModuleVersionDescriptor extends ModuleVersionDescripto
     ModuleVersionResolveException getFailure();
 
     /**
-     * Marks the module version as resolved, with the given meta-data.
+     * Marks the module version as resolved, with the given meta-data and provides the related CachedModuleDescriptor.
      */
-    void resolved(ModuleDescriptor descriptor, boolean changing);
+    void resolved(ModuleDescriptor descriptor, boolean changing, ModuleSource moduleSource);
 
     /**
      * Marks the resolve as failed with the given exception.
@@ -56,4 +56,10 @@ public interface BuildableModuleVersionDescriptor extends ModuleVersionDescripto
      */
     void probablyMissing();
 
+    /**
+     * The ModuleSource of the buildable result
+     */
+    public ModuleSource getModuleSource();
+
+
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CacheLockingModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CacheLockingModuleVersionRepository.java
index cac2014..db6a154 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CacheLockingModuleVersionRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CacheLockingModuleVersionRepository.java
@@ -52,10 +52,10 @@ public class CacheLockingModuleVersionRepository implements ModuleVersionReposit
         });
     }
 
-    public void resolve(final Artifact artifact, final BuildableArtifactResolveResult result) {
+    public void resolve(final Artifact artifact, final BuildableArtifactResolveResult result, final ModuleSource moduleSource) {
         cacheLockingManager.longRunningOperation(String.format("Download %s using repository %s", artifact, getId()), new Runnable() {
             public void run() {
-                repository.resolve(artifact, result);
+                repository.resolve(artifact, result, moduleSource);
             }
         });
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepository.java
index dfe38e0..4b36bd1 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepository.java
@@ -17,7 +17,6 @@ package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
 import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.artifacts.ArtifactIdentifier;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
@@ -30,21 +29,22 @@ import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResu
 import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ForceChangeDependencyDescriptor;
 import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ModuleResolutionCache;
 import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleDescriptorCache;
-import org.gradle.api.internal.externalresource.cached.CachedExternalResource;
-import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
+import org.gradle.api.internal.externalresource.cached.CachedArtifact;
+import org.gradle.api.internal.externalresource.cached.CachedArtifactIndex;
 import org.gradle.api.internal.externalresource.ivy.ArtifactAtRepositoryKey;
 import org.gradle.internal.TimeProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
+import java.math.BigInteger;
 
 public class CachingModuleVersionRepository implements LocalAwareModuleVersionRepository {
     private static final Logger LOGGER = LoggerFactory.getLogger(CachingModuleVersionRepository.class);
 
     private final ModuleResolutionCache moduleResolutionCache;
     private final ModuleDescriptorCache moduleDescriptorCache;
-    private final CachedExternalResourceIndex<ArtifactAtRepositoryKey> artifactAtRepositoryCachedResolutionIndex;
+    private final CachedArtifactIndex artifactAtRepositoryCachedResolutionIndex;
 
     private final CachePolicy cachePolicy;
 
@@ -52,7 +52,7 @@ public class CachingModuleVersionRepository implements LocalAwareModuleVersionRe
     private final TimeProvider timeProvider;
 
     public CachingModuleVersionRepository(ModuleVersionRepository delegate, ModuleResolutionCache moduleResolutionCache, ModuleDescriptorCache moduleDescriptorCache,
-                                          CachedExternalResourceIndex<ArtifactAtRepositoryKey> artifactAtRepositoryCachedResolutionIndex,
+                                          CachedArtifactIndex artifactAtRepositoryCachedResolutionIndex,
                                           CachePolicy cachePolicy, TimeProvider timeProvider) {
         this.delegate = delegate;
         this.moduleDescriptorCache = moduleDescriptorCache;
@@ -88,11 +88,15 @@ public class CachingModuleVersionRepository implements LocalAwareModuleVersionRe
         delegate.getDependency(ForceChangeDependencyDescriptor.forceChangingFlag(dependencyDescriptor, true), result);
         switch (result.getState()) {
             case Missing:
-                moduleDescriptorCache.cacheModuleDescriptor(delegate, dependencyDescriptor.getDependencyRevisionId(), null, dependencyDescriptor.isChanging());
+                final ModuleRevisionId dependencyRevisionId = dependencyDescriptor.getDependencyRevisionId();
+                final DefaultModuleVersionIdentifier moduleVersionIdentifier = new DefaultModuleVersionIdentifier(dependencyRevisionId.getOrganisation(), dependencyRevisionId.getName(), dependencyRevisionId.getRevision());
+                moduleDescriptorCache.cacheModuleDescriptor(delegate, moduleVersionIdentifier, null, null, dependencyDescriptor.isChanging());
                 break;
             case Resolved:
                 moduleResolutionCache.cacheModuleResolution(delegate, dependencyDescriptor.getDependencyRevisionId(), result.getId());
-                moduleDescriptorCache.cacheModuleDescriptor(delegate, result.getId(), result.getDescriptor(), isChangingDependency(dependencyDescriptor, result));
+                final ModuleSource moduleSource = result.getModuleSource();
+                final ModuleDescriptorCache.CachedModuleDescriptor cachedModuleDescriptor = moduleDescriptorCache.cacheModuleDescriptor(delegate, result.getId(), result.getDescriptor(), moduleSource, isChangingDependency(dependencyDescriptor, result));
+                result.resolved(result.getDescriptor(), result.isChanging(), new CachingModuleSource(cachedModuleDescriptor.getDescriptorHash(), cachedModuleDescriptor.isChangingModule(), moduleSource));
                 break;
             case Failed:
                 break;
@@ -106,14 +110,13 @@ public class CachingModuleVersionRepository implements LocalAwareModuleVersionRe
         ModuleResolutionCache.CachedModuleResolution cachedModuleResolution = moduleResolutionCache.getCachedModuleResolution(repository, originalId);
         if (cachedModuleResolution != null && cachedModuleResolution.isDynamicVersion()) {
             ModuleVersionSelector selector = createModuleVersionSelector(originalId);
-            ModuleVersionIdentifier resolvedVersion = cachedModuleResolution.getResolvedModule() == null ? null : cachedModuleResolution.getResolvedModule().getId();
+            ModuleVersionIdentifier resolvedVersion = cachedModuleResolution.getResolvedVersion();
             if (cachePolicy.mustRefreshDynamicVersion(selector, resolvedVersion, cachedModuleResolution.getAgeMillis())) {
                 LOGGER.debug("Resolved revision in dynamic revision cache is expired: will perform fresh resolve of '{}' in '{}'", selector, repository.getName());
                 return original;
             } else {
-                LOGGER.debug("Found resolved revision in dynamic revision cache of '{}': Using '{}' for '{}'",
-                        new Object[]{repository.getName(), cachedModuleResolution.getResolvedVersion(), originalId});
-                return original.clone(cachedModuleResolution.getResolvedVersion());
+                LOGGER.debug("Found resolved revision in dynamic revision cache of '{}': Using '{}' for '{}'", repository.getName(), cachedModuleResolution.getResolvedVersion(), originalId);
+                return original.clone(ModuleRevisionId.newInstance(resolvedVersion.getGroup(), resolvedVersion.getName(), resolvedVersion.getVersion()));
             }
         }
         return original;
@@ -122,7 +125,7 @@ public class CachingModuleVersionRepository implements LocalAwareModuleVersionRe
     public void lookupModuleInCache(ModuleVersionRepository repository, DependencyDescriptor resolvedDependencyDescriptor, BuildableModuleVersionDescriptor result) {
         ModuleRevisionId resolvedModuleVersionId = resolvedDependencyDescriptor.getDependencyRevisionId();
         ModuleVersionIdentifier moduleVersionIdentifier = createModuleVersionIdentifier(resolvedModuleVersionId);
-        ModuleDescriptorCache.CachedModuleDescriptor cachedModuleDescriptor = moduleDescriptorCache.getCachedModuleDescriptor(repository, resolvedModuleVersionId);
+        ModuleDescriptorCache.CachedModuleDescriptor cachedModuleDescriptor = moduleDescriptorCache.getCachedModuleDescriptor(repository, moduleVersionIdentifier);
         if (cachedModuleDescriptor == null) {
             return;
         }
@@ -143,7 +146,6 @@ public class CachingModuleVersionRepository implements LocalAwareModuleVersionRe
         }
         if (cachedModuleDescriptor.isChangingModule() || resolvedDependencyDescriptor.isChanging()) {
             if (cachePolicy.mustRefreshChangingModule(moduleVersionIdentifier, cachedModuleDescriptor.getModuleVersion(), cachedModuleDescriptor.getAgeMillis())) {
-                expireArtifactsForChangingModule(repository, cachedModuleDescriptor.getModuleDescriptor());
                 LOGGER.debug("Cached meta-data for changing module is expired: will perform fresh resolve of '{}' in '{}'", resolvedModuleVersionId, repository.getName());
                 return;
             }
@@ -157,55 +159,49 @@ public class CachingModuleVersionRepository implements LocalAwareModuleVersionRe
 
         LOGGER.debug("Using cached module metadata for module '{}' in '{}'", resolvedModuleVersionId, repository.getName());
         // TODO:DAZ Could provide artifact metadata and file here from artifactFileStore (it's not needed currently)
-        result.resolved(cachedModuleDescriptor.getModuleDescriptor(), cachedModuleDescriptor.isChangingModule());
-    }
-
-    private void expireArtifactsForChangingModule(ModuleVersionRepository repository, ModuleDescriptor descriptor) {
-        for (Artifact artifact : descriptor.getAllArtifacts()) {
-            artifactAtRepositoryCachedResolutionIndex.clear(new ArtifactAtRepositoryKey(repository, artifact.getId()));
-        }
+        result.resolved(cachedModuleDescriptor.getModuleDescriptor(), cachedModuleDescriptor.isChangingModule(), new CachingModuleSource(cachedModuleDescriptor.getDescriptorHash(), cachedModuleDescriptor.isChangingModule(), cachedModuleDescriptor.getModuleSource()));
     }
 
     private boolean isChangingDependency(DependencyDescriptor descriptor, ModuleVersionDescriptor downloadedModule) {
         if (descriptor.isChanging()) {
             return true;
         }
-
         return downloadedModule.isChanging();
     }
 
-    public void resolve(Artifact artifact, BuildableArtifactResolveResult result) {
+    public void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource) {
         ArtifactAtRepositoryKey resolutionCacheIndexKey = new ArtifactAtRepositoryKey(delegate, artifact.getId());
-
         // Look in the cache for this resolver
-        CachedExternalResource cached = artifactAtRepositoryCachedResolutionIndex.lookup(resolutionCacheIndexKey);
-
+        CachedArtifact cached = artifactAtRepositoryCachedResolutionIndex.lookup(resolutionCacheIndexKey);
+        final CachingModuleSource cachedModuleSource = (CachingModuleSource) moduleSource;
+        final BigInteger descriptorHash = cachedModuleSource.getDescriptorHash();
         if (cached != null) {
             ArtifactIdentifier artifactIdentifier = createArtifactIdentifier(artifact);
             long age = timeProvider.getCurrentTime() - cached.getCachedAt();
+            final boolean isChangingModule = cachedModuleSource.isChangingModule();
             if (cached.isMissing()) {
-                if (!cachePolicy.mustRefreshArtifact(artifactIdentifier, null, age)) {
+                if (!cachePolicy.mustRefreshArtifact(artifactIdentifier, null, age, isChangingModule, descriptorHash.equals(cached.getDescriptorHash()))) {
                     LOGGER.debug("Detected non-existence of artifact '{}' in resolver cache", artifact.getId());
                     result.notFound(artifact);
                     return;
                 }
             } else {
                 File cachedArtifactFile = cached.getCachedFile();
-                if (!cachePolicy.mustRefreshArtifact(artifactIdentifier, cachedArtifactFile, age)) {
+                if (!cachePolicy.mustRefreshArtifact(artifactIdentifier, cachedArtifactFile, age, isChangingModule, descriptorHash.equals(cached.getDescriptorHash()))) {
                     LOGGER.debug("Found artifact '{}' in resolver cache: {}", artifact.getId(), cachedArtifactFile);
-                    result.resolved(cachedArtifactFile, cached.getExternalResourceMetaData());
+                    result.resolved(cachedArtifactFile);
                     return;
                 }
             }
         }
 
-        delegate.resolve(artifact, result);
+        delegate.resolve(artifact, result, cachedModuleSource.getDelegate());
         LOGGER.debug("Downloaded artifact '{}' from resolver: {}", artifact.getId(), delegate);
 
         if (result.getFailure() instanceof ArtifactNotFoundException) {
-            artifactAtRepositoryCachedResolutionIndex.storeMissing(resolutionCacheIndexKey);
+            artifactAtRepositoryCachedResolutionIndex.storeMissing(resolutionCacheIndexKey, descriptorHash);
         } else {
-            artifactAtRepositoryCachedResolutionIndex.store(resolutionCacheIndexKey, result.getFile(), result.getExternalResourceMetaData());
+            artifactAtRepositoryCachedResolutionIndex.store(resolutionCacheIndexKey, result.getFile(), descriptorHash);
         }
     }
 
@@ -221,4 +217,28 @@ public class CachingModuleVersionRepository implements LocalAwareModuleVersionRe
         ModuleVersionIdentifier moduleVersionIdentifier = createModuleVersionIdentifier(artifact.getModuleRevisionId());
         return new DefaultArtifactIdentifier(moduleVersionIdentifier, artifact.getName(), artifact.getType(), artifact.getExt(), artifact.getExtraAttribute("classifier"));
     }
+
+    static class CachingModuleSource implements ModuleSource {
+        private final BigInteger descriptorHash;
+        private final boolean changingModule;
+        private final ModuleSource delegate;
+
+        public CachingModuleSource(BigInteger descriptorHash, boolean changingModule, ModuleSource delegate) {
+            this.delegate = delegate;
+            this.descriptorHash = descriptorHash;
+            this.changingModule = changingModule;
+        }
+
+        public BigInteger getDescriptorHash() {
+            return descriptorHash;
+        }
+
+        public boolean isChangingModule() {
+            return changingModule;
+        }
+
+        public ModuleSource getDelegate() {
+            return delegate;
+        }
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ChangingModuleDetector.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ChangingModuleDetector.java
index abe8b46..5beef9e 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ChangingModuleDetector.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ChangingModuleDetector.java
@@ -25,7 +25,7 @@ import org.gradle.api.GradleException;
 
 import java.lang.reflect.Method;
 
-class ChangingModuleDetector {
+public class ChangingModuleDetector {
     private final DependencyResolver resolver;
 
     public ChangingModuleDetector(DependencyResolver resolver) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptor.java
index ed03d4b..3ba4306 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptor.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptor.java
@@ -17,13 +17,18 @@ package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
 import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
 
 public class DefaultBuildableModuleVersionDescriptor implements BuildableModuleVersionDescriptor {
     private ModuleDescriptor moduleDescriptor;
     private boolean changing;
     private State state = State.Unknown;
+    private ModuleSource moduleSource;
+
     private ModuleVersionResolveException failure;
+    private ModuleVersionIdentifier moduleVersionIdentifier;
 
     public void reset(State state) {
         this.state = state;
@@ -32,10 +37,13 @@ public class DefaultBuildableModuleVersionDescriptor implements BuildableModuleV
         failure = null;
     }
 
-    public void resolved(ModuleDescriptor descriptor, boolean changing) {
+    public void resolved(ModuleDescriptor descriptor, boolean changing, ModuleSource moduleSource) {
         reset(State.Resolved);
         moduleDescriptor = descriptor;
         this.changing = changing;
+        final ModuleRevisionId moduleRevisionId = descriptor.getModuleRevisionId();
+        this.moduleVersionIdentifier = new DefaultModuleVersionIdentifier(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
+        this.moduleSource = moduleSource;
     }
 
     public void missing() {
@@ -75,9 +83,9 @@ public class DefaultBuildableModuleVersionDescriptor implements BuildableModuleV
         }
     }
 
-    public ModuleRevisionId getId() {
+    public ModuleVersionIdentifier getId() {
         assertResolved();
-        return moduleDescriptor.getResolvedModuleRevisionId();
+        return moduleVersionIdentifier;
     }
 
     public ModuleDescriptor getDescriptor() {
@@ -89,4 +97,9 @@ public class DefaultBuildableModuleVersionDescriptor implements BuildableModuleV
         assertResolved();
         return changing;
     }
+
+    public ModuleSource getModuleSource() {
+        assertResolved();
+        return moduleSource;
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ExternalResourceResolverAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ExternalResourceResolverAdapter.java
index 7a67340..4cd82e0 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ExternalResourceResolverAdapter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ExternalResourceResolverAdapter.java
@@ -16,12 +16,9 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
-import org.gradle.api.internal.artifacts.repositories.cachemanager.EnhancedArtifactDownloadReport;
 import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
-import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
-
-import java.io.File;
 
 /**
  * A {@link ModuleVersionRepository} wrapper around an {@link ExternalResourceResolver}.
@@ -34,21 +31,11 @@ public class ExternalResourceResolverAdapter extends AbstractDependencyResolverA
         this.resolver = resolver;
     }
 
-    public void resolve(Artifact artifact, BuildableArtifactResolveResult result) {
-        EnhancedArtifactDownloadReport artifactDownloadReport = resolver.download(artifact);
-        if (downloadFailed(artifactDownloadReport)) {
-            result.failed(new ArtifactResolveException(artifactDownloadReport.getArtifact(), artifactDownloadReport.getFailure()));
-            return;
-        }
-
-        ArtifactOriginWithMetaData artifactOrigin = artifactDownloadReport.getArtifactOrigin();
+    public void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
+        resolver.getDependency(dependencyDescriptor, result);
+    }
 
-        File localFile = artifactDownloadReport.getLocalFile();
-        if (localFile != null) {
-            ExternalResourceMetaData metaData = artifactOrigin.getMetaData();
-            result.resolved(localFile, metaData);
-        } else {
-            result.notFound(artifact);
-        }
+    public void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource) {
+        resolver.resolve(artifact, result, moduleSource);
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDependencyResolverAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDependencyResolverAdapter.java
index 92dba7b..100f9c8 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDependencyResolverAdapter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDependencyResolverAdapter.java
@@ -15,28 +15,51 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
-import org.apache.ivy.core.cache.ArtifactOrigin;
 import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadStatus;
 import org.apache.ivy.core.resolve.DownloadOptions;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
 import org.gradle.api.internal.artifacts.repositories.cachemanager.EnhancedArtifactDownloadReport;
-import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.internal.UncheckedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.File;
+import java.text.ParseException;
 
 /**
  * A {@link ModuleVersionRepository} wrapper around an Ivy {@link DependencyResolver}.
  */
 public class IvyDependencyResolverAdapter extends AbstractDependencyResolverAdapter {
+    private static final Logger LOGGER = LoggerFactory.getLogger(IvyDependencyResolverAdapter.class);
     private final DownloadOptions downloadOptions = new DownloadOptions();
 
     public IvyDependencyResolverAdapter(DependencyResolver resolver) {
         super(resolver);
     }
 
-    public void resolve(Artifact artifact, BuildableArtifactResolveResult result) {
+    public void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
+        ResolveData resolveData = IvyContextualiser.getIvyContext().getResolveData();
+        try {
+            ResolvedModuleRevision revision = resolver.getDependency(dependencyDescriptor, resolveData);
+            if (revision == null) {
+                LOGGER.debug("Performed resolved of module '{}' in repository '{}': not found", dependencyDescriptor.getDependencyRevisionId(), getName());
+                result.missing();
+            } else {
+                LOGGER.debug("Performed resolved of module '{}' in repository '{}': found", dependencyDescriptor.getDependencyRevisionId(), getName());
+                result.resolved(revision.getDescriptor(), isChanging(revision), null);
+            }
+        } catch (ParseException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource) {
         ArtifactDownloadReport artifactDownloadReport = resolver.download(new Artifact[]{artifact}, downloadOptions).getArtifactReport(artifact);
         if (downloadFailed(artifactDownloadReport)) {
             if (artifactDownloadReport instanceof EnhancedArtifactDownloadReport) {
@@ -48,17 +71,21 @@ public class IvyDependencyResolverAdapter extends AbstractDependencyResolverAdap
             return;
         }
 
-        ArtifactOrigin artifactOrigin = artifactDownloadReport.getArtifactOrigin();
-
         File localFile = artifactDownloadReport.getLocalFile();
         if (localFile != null) {
-            ExternalResourceMetaData metaData = null;
-            if (artifactOrigin instanceof ArtifactOriginWithMetaData) {
-                metaData = ((ArtifactOriginWithMetaData) artifactOrigin).getMetaData();
-            }
-            result.resolved(localFile, metaData);
+            result.resolved(localFile);
         } else {
             result.notFound(artifact);
         }
     }
+
+    private boolean downloadFailed(ArtifactDownloadReport artifactReport) {
+        // Ivy reports FAILED with MISSING_ARTIFACT message when the artifact doesn't exist.
+        return artifactReport.getDownloadStatus() == DownloadStatus.FAILED
+                && !artifactReport.getDownloadDetails().equals(ArtifactDownloadReport.MISSING_ARTIFACT);
+    }
+
+    private boolean isChanging(ResolvedModuleRevision resolvedModuleRevision) {
+        return new ChangingModuleDetector(resolver).isChangingModule(resolvedModuleRevision.getDescriptor());
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolver.java
index b9304a0..6185f63 100755
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolver.java
@@ -22,7 +22,11 @@ import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.apache.ivy.core.module.id.ModuleId;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.apache.ivy.plugins.version.VersionMatcher;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
 import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
 
 /**
  * A {@link org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleVersionIdResolver} implementation which returns lazy resolvers that don't actually retrieve module descriptors until
@@ -70,8 +74,9 @@ public class LazyDependencyToModuleResolver implements DependencyToModuleVersion
             this.dependencyDescriptor = dependencyDescriptor;
         }
 
-        public ModuleRevisionId getId() throws ModuleVersionResolveException {
-            return dependencyDescriptor.getDependencyRevisionId();
+        public ModuleVersionIdentifier getId() throws ModuleVersionResolveException {
+            final ModuleRevisionId dependencyRevisionId = dependencyDescriptor.getDependencyRevisionId();
+            return new DefaultModuleVersionIdentifier(dependencyRevisionId.getOrganisation(), dependencyRevisionId.getName(), dependencyRevisionId.getRevision());
         }
 
         public ModuleVersionResolveException getFailure() {
@@ -103,8 +108,8 @@ public class LazyDependencyToModuleResolver implements DependencyToModuleVersion
             return resolveResult;
         }
 
-        public IdSelectionReason getSelectionReason() {
-            return IdSelectionReason.requested;
+        public ModuleVersionSelectionReason getSelectionReason() {
+            return VersionSelectionReasons.REQUESTED;
         }
 
         private void checkDescriptor(ModuleDescriptor descriptor) {
@@ -115,8 +120,7 @@ public class LazyDependencyToModuleResolver implements DependencyToModuleVersion
             for (Configuration configuration : descriptor.getConfigurations()) {
                 for (String parent : configuration.getExtends()) {
                     if (descriptor.getConfiguration(parent) == null) {
-                        throw new ModuleVersionResolveException(String.format("Configuration '%s' extends unknown configuration '%s' in module descriptor for group:%s, module:%s, version:%s.",
-                                configuration.getName(), parent, id.getOrganisation(), id.getName(), id.getRevision()));
+                        throw new ModuleVersionResolveException(id, String.format("Configuration '%s' extends unknown configuration '%s' in module descriptor for %%s.", configuration.getName(), parent));
                     }
                 }
             }
@@ -132,7 +136,7 @@ public class LazyDependencyToModuleResolver implements DependencyToModuleVersion
         }
 
         protected void onUnexpectedModuleRevisionId(ModuleDescriptor descriptor) {
-            throw new ModuleVersionResolveException(String.format("Received unexpected module descriptor %s for dependency %s.", descriptor.getModuleRevisionId(), dependencyDescriptor.getDependencyRevisionId()));
+            throw new ModuleVersionResolveException(dependencyDescriptor.getDependencyRevisionId(), String.format("Received unexpected module descriptor %s for dependency %%s.", descriptor.getModuleRevisionId()));
         }
     }
 
@@ -147,13 +151,13 @@ public class LazyDependencyToModuleResolver implements DependencyToModuleVersion
         }
 
         @Override
-        public ModuleRevisionId getId() throws ModuleVersionResolveException {
+        public ModuleVersionIdentifier getId() throws ModuleVersionResolveException {
             return resolve().getId();
         }
 
         @Override
         protected ModuleVersionNotFoundException notFound(ModuleRevisionId id) {
-            return new ModuleVersionNotFoundException(String.format("Could not find any version that matches group:%s, module:%s, version:%s.", id.getOrganisation(), id.getName(), id.getRevision()));
+            return new ModuleVersionNotFoundException(id, "Could not find any version that matches %s.");
         }
 
         @Override
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalModuleVersionRepository.java
index 4391321..8e01d79 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalModuleVersionRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalModuleVersionRepository.java
@@ -31,8 +31,8 @@ public class LocalModuleVersionRepository implements LocalAwareModuleVersionRepo
         return delegate.isLocal();
     }
 
-    public void resolve(Artifact artifact, BuildableArtifactResolveResult result) {
-        delegate.resolve(artifact, result);
+    public void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource) {
+        delegate.resolve(artifact, result, moduleSource);
     }
 
     public void getLocalDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleSource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleSource.java
new file mode 100644
index 0000000..61e2285
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleSource.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
+
+import java.io.Serializable;
+
+public interface ModuleSource extends Serializable {
+}
+
+
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionDescriptor.java
index 4fd523d..8a6d48a 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionDescriptor.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionDescriptor.java
@@ -16,11 +16,11 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
 
 // at some point, this is due to merge with ModuleVersionResolveResult
 public interface ModuleVersionDescriptor {
-    ModuleRevisionId getId();
+    ModuleVersionIdentifier getId();
 
     ModuleDescriptor getDescriptor();
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionRepository.java
index d77c451..216493d 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionRepository.java
@@ -17,7 +17,6 @@ package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
 import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.gradle.api.internal.artifacts.ivyservice.ArtifactResolver;
 import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
 
 /**
@@ -25,7 +24,7 @@ import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResu
  *
  * The plan is to sync this with {@link org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver}
  */
-public interface ModuleVersionRepository extends ArtifactResolver {
+public interface ModuleVersionRepository {
     String getId();
 
     String getName();
@@ -35,7 +34,7 @@ public interface ModuleVersionRepository extends ArtifactResolver {
     /**
      * Downloads the given artifact. Any failures are packaged up in the result.
      */
-    void resolve(Artifact artifact, BuildableArtifactResolveResult result);
+    void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource);
 
     // TODO - should be internal to the implementation of this (is only used to communicate IvyDependencyResolverAdapter -> CachingModuleVersionRepository)
     boolean isLocal();
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java
index f72ec34..29603b2 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java
@@ -29,8 +29,7 @@ import org.gradle.api.internal.artifacts.ivyservice.SettingsConverter;
 import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ModuleResolutionCache;
 import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleDescriptorCache;
 import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
-import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
-import org.gradle.api.internal.externalresource.ivy.ArtifactAtRepositoryKey;
+import org.gradle.api.internal.externalresource.cached.CachedArtifactIndex;
 import org.gradle.internal.TimeProvider;
 import org.gradle.util.WrapUtil;
 
@@ -42,14 +41,14 @@ public class ResolveIvyFactory {
     private final SettingsConverter settingsConverter;
     private final ModuleResolutionCache moduleResolutionCache;
     private final ModuleDescriptorCache moduleDescriptorCache;
-    private final CachedExternalResourceIndex<ArtifactAtRepositoryKey> artifactAtRepositoryCachedResolutionIndex;
+    private final CachedArtifactIndex artifactAtRepositoryCachedResolutionIndex;
     private final CacheLockingManager cacheLockingManager;
     private final StartParameterResolutionOverride startParameterResolutionOverride;
     private final TimeProvider timeProvider;
 
     public ResolveIvyFactory(IvyFactory ivyFactory, ResolverProvider resolverProvider, SettingsConverter settingsConverter,
                              ModuleResolutionCache moduleResolutionCache, ModuleDescriptorCache moduleDescriptorCache,
-                             CachedExternalResourceIndex<ArtifactAtRepositoryKey> artifactAtRepositoryCachedResolutionIndex,
+                             CachedArtifactIndex artifactAtRepositoryCachedResolutionIndex,
                              CacheLockingManager cacheLockingManager, StartParameterResolutionOverride startParameterResolutionOverride,
                              TimeProvider timeProvider) {
         this.ivyFactory = ivyFactory;
@@ -106,6 +105,4 @@ public class ResolveIvyFactory {
         options.setConfs(WrapUtil.toArray(configurationName));
         return new ResolveData(ivy.getResolveEngine(), options);
     }
-
-
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java
index 30f5aa7..98a85ab 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java
@@ -98,10 +98,10 @@ public class StartParameterResolutionOverride {
         }
 
         public void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
-            result.failed(new ModuleVersionResolveException("No cached version available for offline mode"));
+            result.failed(new ModuleVersionResolveException(dependencyDescriptor.getDependencyRevisionId(), "No cached version of %s available for offline mode."));
         }
 
-        public void resolve(Artifact artifact, BuildableArtifactResolveResult result) {
+        public void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource) {
             result.failed(new ArtifactResolveException(artifact, "No cached version available for offline mode"));
         }
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChain.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChain.java
index dd0b78a..264b75a 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChain.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChain.java
@@ -16,15 +16,16 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
+import org.apache.ivy.core.module.descriptor.Artifact;
 import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.apache.ivy.plugins.latest.ArtifactInfo;
 import org.apache.ivy.plugins.latest.ComparatorLatestStrategy;
 import org.apache.ivy.plugins.resolver.ResolverSettings;
-import org.gradle.api.internal.artifacts.ivyservice.BuildableModuleVersionResolveResult;
-import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver;
-import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -47,7 +48,8 @@ public class UserResolverChain implements DependencyToModuleResolver {
     }
 
     public void resolve(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionResolveResult result) {
-        LOGGER.debug("Attempting to resolve module '{}' using repositories {}", dependencyDescriptor.getDependencyRevisionId(), moduleVersionRepositoryNames);
+        final ModuleRevisionId dependencyRevisionId = dependencyDescriptor.getDependencyRevisionId();
+        LOGGER.debug("Attempting to resolve module '{}' using repositories {}", dependencyRevisionId, moduleVersionRepositoryNames);
         List<Throwable> errors = new ArrayList<Throwable>();
         final ModuleResolution latestResolved = findLatestModule(dependencyDescriptor, errors);
         if (latestResolved != null) {
@@ -56,13 +58,14 @@ public class UserResolverChain implements DependencyToModuleResolver {
             for (Throwable error : errors) {
                 LOGGER.debug("Discarding resolve failure.", error);
             }
-            result.resolved(latestResolved.getId(), latestResolved.getDescriptor(), latestResolved.repository);
+            result.resolved(latestResolved.getId(), latestResolved.getDescriptor(), new ModuleVersionRepositoryArtifactResolverAdapter(latestResolved.repository, latestResolved.moduleSource));
             return;
         }
         if (!errors.isEmpty()) {
-            result.failed(new ModuleVersionResolveException(dependencyDescriptor.getDependencyRevisionId(), errors));
+            result.failed(new ModuleVersionResolveException(dependencyRevisionId, errors));
         } else {
-            result.notFound(dependencyDescriptor.getDependencyRevisionId());
+            final DefaultModuleVersionIdentifier moduleVersionIdentifier = new DefaultModuleVersionIdentifier(dependencyRevisionId.getOrganisation(), dependencyRevisionId.getName(), dependencyRevisionId.getRevision());
+            result.notFound(moduleVersionIdentifier);
         }
     }
 
@@ -112,7 +115,7 @@ public class UserResolverChain implements DependencyToModuleResolver {
                     }
                     break;
                 case Resolved:
-                    ModuleResolution moduleResolution = new ModuleResolution(request.repository, request.descriptor);
+                    ModuleResolution moduleResolution = new ModuleResolution(request.repository, request.descriptor, request.descriptor.getModuleSource());
                     if (isStaticVersion && !moduleResolution.isGeneratedModuleDescriptor()) {
                         return moduleResolution;
                     }
@@ -148,9 +151,24 @@ public class UserResolverChain implements DependencyToModuleResolver {
         return comparison < 0 ? two : one;
     }
 
+    private static class ModuleVersionRepositoryArtifactResolverAdapter implements ArtifactResolver {
+        private final ModuleVersionRepository delegate;
+        private final ModuleSource moduleSource;
+
+        public ModuleVersionRepositoryArtifactResolverAdapter(ModuleVersionRepository repository, ModuleSource moduleSource) {
+            this.delegate = repository;
+            this.moduleSource = moduleSource;
+        }
+
+        public void resolve(Artifact artifact, BuildableArtifactResolveResult result) {
+            delegate.resolve(artifact, result, moduleSource);
+        }
+    }
+
     private static class RepositoryResolveState {
         final LocalAwareModuleVersionRepository repository;
         final DefaultBuildableModuleVersionDescriptor descriptor = new DefaultBuildableModuleVersionDescriptor();
+
         boolean searchedLocally;
         boolean searchedRemotely;
 
@@ -179,13 +197,15 @@ public class UserResolverChain implements DependencyToModuleResolver {
     private static class ModuleResolution implements ArtifactInfo {
         public final ModuleVersionRepository repository;
         public final ModuleVersionDescriptor module;
+        public final ModuleSource moduleSource;
 
-        public ModuleResolution(ModuleVersionRepository repository, ModuleVersionDescriptor module) {
+        public ModuleResolution(ModuleVersionRepository repository, ModuleVersionDescriptor module, ModuleSource moduleSource) {
             this.repository = repository;
             this.module = module;
+            this.moduleSource = moduleSource;
         }
 
-        public ModuleRevisionId getId() throws ModuleVersionResolveException {
+        public ModuleVersionIdentifier getId() throws ModuleVersionResolveException {
             return module.getId();
         }
 
@@ -202,7 +222,7 @@ public class UserResolverChain implements DependencyToModuleResolver {
         }
 
         public String getRevision() {
-            return module.getId().getRevision();
+            return module.getId().getVersion();
         }
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParser.java
index cb5c3aa..f1758d3 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParser.java
@@ -17,19 +17,17 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
 
 import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.apache.ivy.plugins.parser.ParserSettings;
-import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
 import org.apache.ivy.plugins.repository.Resource;
 
 import java.io.IOException;
 import java.net.URL;
 import java.text.ParseException;
 
-public class DownloadedIvyModuleDescriptorParser extends XmlModuleDescriptorParser {
+public class DownloadedIvyModuleDescriptorParser extends IvyXmlModuleDescriptorParser {
     @Override
-    synchronized public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL xmlURL, Resource res, boolean validate) throws ParseException, IOException {
-        DefaultModuleDescriptor descriptor = (DefaultModuleDescriptor) super.parseDescriptor(ivySettings, xmlURL, res, validate);
+    public DefaultModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL xmlURL, Resource res, boolean validate) throws ParseException, IOException {
+        DefaultModuleDescriptor descriptor = super.parseDescriptor(ivySettings, xmlURL, res, validate);
         descriptor.setDefault(false);
         return descriptor;
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorBuilder.java
index e0eceaa..fe8d415 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorBuilder.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorBuilder.java
@@ -188,9 +188,9 @@ public class GradlePomModuleDescriptorBuilder {
         return ivyModuleDescriptor;
     }
 
-    public void setModuleRevId(String groupId, String artifactId, String version) {
-        mrid = ModuleRevisionId.newInstance(groupId, artifactId, version);
-        ivyModuleDescriptor.setModuleRevisionId(mrid);
+    public void setModuleRevId(ModuleRevisionId mrid, String group, String module, String version) {
+        this.mrid = mrid;
+        ivyModuleDescriptor.setModuleRevisionId(ModuleRevisionId.newInstance(group, module, version));
 
         if ((version == null) || version.endsWith("SNAPSHOT")) {
             ivyModuleDescriptor.setStatus("integration");
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java
index 8762833..cdfec37 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java
@@ -124,7 +124,8 @@ public final class GradlePomModuleDescriptorParser implements ModuleDescriptorPa
             String groupId = domReader.getGroupId();
             String artifactId = domReader.getArtifactId();
             String version = domReader.getVersion();
-            mdBuilder.setModuleRevId(groupId, artifactId, version);
+            ModuleScopedParserSettings scopedSettings = (ModuleScopedParserSettings) ivySettings;
+            mdBuilder.setModuleRevId(scopedSettings.getCurrentRevisionId(), groupId, artifactId, version);
 
             mdBuilder.setHomePage(domReader.getHomePage());
             mdBuilder.setDescription(domReader.getDescription());
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParser.java
new file mode 100644
index 0000000..80e9fda
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParser.java
@@ -0,0 +1,1206 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.module.descriptor.*;
+import org.apache.ivy.core.module.id.ArtifactId;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolveEngine;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+import org.apache.ivy.plugins.conflict.FixedConflictManager;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.namespace.NameSpaceHelper;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.parser.AbstractModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.url.URLResource;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.XMLHelper;
+import org.apache.ivy.util.extendable.ExtendableItemHelper;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Copied from org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser into gradle codebase to make
+ * it thread-safe.
+ */
+public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser {
+    static final String[] DEPENDENCY_REGULAR_ATTRIBUTES = new String[] {"org", "name", "branch",
+            "branchConstraint", "rev", "revConstraint", "force", "transitive", "changing", "conf"};
+
+    public static final String IVY_DATE_FORMAT_PATTERN = "yyyyMMddHHmmss";
+
+    /**
+     * @param xmlURL
+     *            the url pointing to the file to parse
+     * @param res
+     *            the real resource to parse, used for log only
+     * @param validate
+     * @return
+     * @throws java.text.ParseException
+     * @throws java.io.IOException
+     */
+    public DefaultModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL xmlURL, Resource res,
+            boolean validate) throws ParseException, IOException {
+        Parser parser = newParser(ivySettings);
+        parser.setValidate(validate);
+        parser.setResource(res);
+        parser.setInput(xmlURL);
+        parser.parse();
+        return (DefaultModuleDescriptor) parser.getModuleDescriptor();
+    }
+
+    /** Used for test purpose */
+    ModuleDescriptor parseDescriptor(ParserSettings ivySettings, InputStream descriptor,
+            Resource res, boolean validate) throws ParseException, IOException {
+        Parser parser = newParser(ivySettings);
+        parser.setValidate(validate);
+        parser.setResource(res);
+        parser.setInput(descriptor);
+        parser.parse();
+        return parser.getModuleDescriptor();
+    }
+
+    /**
+     * Instantiates a Parser instance responsible for actual parsing of Ivy files.
+     * <p>
+     * Override this method if you want to use a custom Parser.
+     * </p>
+     *
+     * @param ivySettings
+     *            the settings to use during parsing
+     * @return the Parser instance used for parsing Ivy files
+     */
+    protected Parser newParser(ParserSettings ivySettings) {
+        return new Parser(this, ivySettings);
+    }
+
+    public boolean accept(Resource res) {
+        return true; // this the default parser, it thus accepts all resources
+    }
+
+    public void toIvyFile(InputStream is, Resource res, File destFile, ModuleDescriptor md)
+            throws IOException, ParseException {
+        throw new UnsupportedOperationException();
+    }
+
+    public static class Parser extends AbstractParser {
+        public static final class State {
+            public static final int NONE = 0;
+
+            public static final int INFO = 1;
+
+            public static final int CONF = 2;
+
+            public static final int PUB = 3;
+
+            public static final int DEP = 4;
+
+            public static final int DEP_ARTIFACT = 5;
+
+            public static final int ARTIFACT_INCLUDE = 6;
+
+            public static final int ARTIFACT_EXCLUDE = 7;
+
+            public static final int CONFLICT = 8;
+
+            public static final int EXCLUDE = 9;
+
+            public static final int DEPS = 10;
+
+            public static final int DESCRIPTION = 11;
+
+            public static final int EXTRA_INFO = 12;
+
+            private State() {
+            }
+        }
+
+        protected static final List ALLOWED_VERSIONS = Arrays.asList(
+                new String[]{"1.0", "1.1", "1.2", "1.3", "1.4", "2.0", "2.1", "2.2"});
+
+        /* how and what do we have to parse */
+        private ParserSettings settings;
+        private boolean validate = true;
+        private URL descriptorURL;
+        private InputStream descriptorInput;
+
+
+        /* Parsing state */
+        private int state = State.NONE;
+        private PatternMatcher defaultMatcher;
+        private DefaultDependencyDescriptor dd;
+        private ConfigurationAware confAware;
+        private MDArtifact artifact;
+        private String conf;
+        private boolean artifactsDeclared;
+        private StringBuffer buffer;
+        private String descriptorVersion;
+        private String[] publicationsDefaultConf;
+
+        public Parser(ModuleDescriptorParser parser, ParserSettings ivySettings) {
+            super(parser);
+            settings = ivySettings;
+        }
+
+        public void setInput(InputStream descriptorInput) {
+            this.descriptorInput = descriptorInput;
+        }
+
+        public void setInput(URL descriptorURL) {
+            this.descriptorURL = descriptorURL;
+        }
+
+        public void setValidate(boolean validate) {
+            this.validate = validate;
+        }
+
+        public void parse() throws ParseException,
+                IOException {
+            try {
+                URL schemaURL = validate ? getSchemaURL() : null;
+                if (descriptorURL != null) {
+                    XMLHelper.parse(descriptorURL, schemaURL, this);
+                } else {
+                    XMLHelper.parse(descriptorInput, schemaURL, this, null);
+                }
+                checkConfigurations();
+                replaceConfigurationWildcards();
+                getMd().setModuleArtifact(
+                    DefaultArtifact.newIvyArtifact(
+                        getMd().getResolvedModuleRevisionId(), getMd().getPublicationDate()));
+                if (!artifactsDeclared) {
+                    String[] confs = getMd().getConfigurationsNames();
+                    for (int i = 0; i < confs.length; i++) {
+                        getMd().addArtifact(confs[i],
+                            new MDArtifact(getMd(), getMd().getModuleRevisionId().getName(),
+                                "jar", "jar"));
+                    }
+                }
+                getMd().check();
+            } catch (ParserConfigurationException ex) {
+                IllegalStateException ise = new IllegalStateException(ex.getMessage() + " in "
+                        + descriptorURL);
+                ise.initCause(ex);
+                throw ise;
+            } catch (Exception ex) {
+                checkErrors();
+                ParseException pe = new ParseException(ex.getMessage() + " in " + descriptorURL, 0);
+                pe.initCause(ex);
+                throw pe;
+            }
+        }
+
+        public void startElement(String uri, String localName, String qName, Attributes attributes)
+                throws SAXException {
+            try {
+                if (state == State.DESCRIPTION) {
+                    // make sure we don't interpret any tag while in description tag
+                    getBuffer().append("<").append(qName);
+                    for (int i = 0; i < attributes.getLength(); i++) {
+                        getBuffer().append(" ");
+                        getBuffer().append(attributes.getQName(i));
+                        getBuffer().append("=\"");
+                        getBuffer().append(attributes.getValue(i));
+                        getBuffer().append("\"");
+                    }
+                    getBuffer().append(">");
+                    return;
+                } else if ("ivy-module".equals(qName)) {
+                    ivyModuleStarted(attributes);
+                } else if ("info".equals(qName)) {
+                    infoStarted(attributes);
+                } else if (state == State.INFO && "extends".equals(qName)) {
+                    extendsStarted(attributes);
+                } else if (state == State.INFO && "license".equals(qName)) {
+                    getMd().addLicense(new License(settings.substitute(attributes.getValue("name")),
+                                        settings.substitute(attributes.getValue("url"))));
+                } else if (state == State.INFO && "description".equals(qName)) {
+                    getMd().setHomePage(settings.substitute(attributes.getValue("homepage")));
+                    state = State.DESCRIPTION;
+                    buffer = new StringBuffer();
+                } else if (state == State.INFO && isOtherNamespace(qName)) {
+                    buffer = new StringBuffer();
+                    state = State.EXTRA_INFO;
+                } else if ("configurations".equals(qName)) {
+                    configurationStarted(attributes);
+                } else if ("publications".equals(qName)) {
+                    publicationsStarted(attributes);
+                } else if ("dependencies".equals(qName)) {
+                    dependenciesStarted(attributes);
+                } else if ("conflicts".equals(qName)) {
+                    if (!descriptorVersion.startsWith("1.")) {
+                        Message.deprecated("using conflicts section is deprecated: "
+                                + "please use hints section instead. Ivy file URL: " + descriptorURL);
+                    }
+                    state = State.CONFLICT;
+                    checkConfigurations();
+                } else if ("artifact".equals(qName)) {
+                    artifactStarted(qName, attributes);
+                } else if ("include".equals(qName) && state == State.DEP) {
+                    addIncludeRule(qName, attributes);
+                } else if ("exclude".equals(qName) && state == State.DEP) {
+                    addExcludeRule(qName, attributes);
+                } else if ("exclude".equals(qName) && state == State.DEPS) {
+                    state = State.EXCLUDE;
+                    parseRule(qName, attributes);
+                    getMd().addExcludeRule((ExcludeRule) confAware);
+                } else if ("dependency".equals(qName)) {
+                    dependencyStarted(attributes);
+                } else if ("conf".equals(qName)) {
+                    confStarted(attributes);
+                } else if ("mapped".equals(qName)) {
+                    dd.addDependencyConfiguration(conf, settings.substitute(attributes
+                            .getValue("name")));
+                } else if (("conflict".equals(qName) && state == State.DEPS)
+                        || "manager".equals(qName) && state == State.CONFLICT) {
+                    managerStarted(attributes, state == State.CONFLICT ? "name" : "manager");
+                } else if ("override".equals(qName) && state == State.DEPS) {
+                    mediationOverrideStarted(attributes);
+                } else if ("include".equals(qName) && state == State.CONF) {
+                    includeConfStarted(attributes);
+                } else if (validate && state != State.EXTRA_INFO && state != State.DESCRIPTION) {
+                    addError("unknown tag " + qName);
+                }
+            } catch (Exception ex) {
+                if (ex instanceof SAXException) {
+                    throw (SAXException) ex;
+                }
+                SAXException sax = new SAXException("Problem occurred while parsing ivy file: "
+                        + ex.getMessage(), ex);
+                sax.initCause(ex);
+                throw sax;
+            }
+        }
+
+        protected String getDefaultParentLocation() {
+            return "../ivy.xml";
+        }
+
+        protected void extendsStarted(Attributes attributes) throws ParseException {
+            String parentOrganisation = attributes.getValue("organisation");
+            String parentModule = attributes.getValue("module");
+            String parentRevision = attributes.getValue("revision");
+            String location = attributes.getValue("location") != null ? attributes
+                    .getValue("location") : getDefaultParentLocation();
+            ModuleDescriptor parent = null;
+
+            String extendType = attributes.getValue("extendType") != null ? attributes.getValue(
+                "extendType").toLowerCase() : "all";
+
+            List/* <String> */extendTypes = Arrays.asList(extendType.split(","));
+
+            try {
+                Message.debug("Trying to parse included ivy file :" + location);
+                parent = parseOtherIvyFileOnFileSystem(location);
+
+                //verify that the parsed descriptor is the correct parent module.
+                ModuleId expected = new ModuleId(parentOrganisation, parentModule);
+                ModuleId pid = parent.getModuleRevisionId().getModuleId();
+                if (!expected.equals(pid)) {
+                    Message.verbose("Ignoring parent Ivy file " + location + "; expected "
+                        + expected + " but found " + pid);
+                    parent = null;
+                }
+
+            } catch (ParseException e) {
+                Message.warn("Unable to parse included ivy file " + location + ": "
+                    + e.getMessage());
+            } catch (IOException e) {
+                Message.warn("Unable to parse included ivy file " + location + ": "
+                    + e.getMessage());
+            }
+
+            // if the included ivy file is not found on file system, tries to resolve using
+            // repositories
+            if (parent == null) {
+                try {
+                    Message.debug(
+                        "Trying to parse included ivy file by asking repository for module :"
+                                    + parentOrganisation
+                                    + "#"
+                                    + parentModule
+                                    + ";"
+                                    + parentRevision);
+                    parent = parseOtherIvyFile(parentOrganisation, parentModule, parentRevision);
+                } catch (ParseException e) {
+                    Message.warn("Unable to parse included ivy file for " + parentOrganisation
+                            + "#" + parentModule + ";" + parentRevision);
+                }
+            }
+
+            if (parent == null) {
+                throw new ParseException("Unable to parse included ivy file for "
+                        + parentOrganisation + "#" + parentModule + ";" + parentRevision, 0);
+            }
+
+            ResolutionCacheManager cacheManager = settings.getResolutionCacheManager();
+
+            File ivyFileInCache = cacheManager.getResolvedIvyFileInCache(parent
+                    .getResolvedModuleRevisionId());
+            //Generate the parent cache file if necessary
+            if (parent.getResource() != null
+                    && !parent.getResource().getName().equals(ivyFileInCache.toURI().toString())) {
+                try {
+                    parent.toIvyFile(ivyFileInCache);
+                } catch (ParseException e) {
+                    throw new ParseException("Unable to create cache file for "
+                            + parentOrganisation + "#" + parentModule + ";" + parentRevision
+                            + " Reason:" + e.getLocalizedMessage(), 0);
+                } catch (IOException e) {
+                    throw new ParseException("Unable to create cache file for "
+                            + parentOrganisation + "#" + parentModule + ";" + parentRevision
+                            + " Reason :" + e.getLocalizedMessage(), 0);
+                }
+            }
+
+            DefaultExtendsDescriptor ed = new DefaultExtendsDescriptor(
+                    parent.getModuleRevisionId(),
+                    parent.getResolvedModuleRevisionId(),
+                    attributes.getValue("location"),
+                    (String[]) extendTypes.toArray(new String[extendTypes.size()]));
+            getMd().addInheritedDescriptor(ed);
+
+            mergeWithOtherModuleDescriptor(extendTypes, parent);
+        }
+
+        protected void mergeWithOtherModuleDescriptor(List/* <String> */extendTypes,
+                ModuleDescriptor parent) {
+
+            if (extendTypes.contains("all")) {
+                mergeAll(parent);
+            } else {
+                if (extendTypes.contains("info")) {
+                    mergeInfo(parent);
+                }
+
+                if (extendTypes.contains("configurations")) {
+                    mergeConfigurations(parent.getModuleRevisionId(), parent.getConfigurations());
+                }
+
+                if (extendTypes.contains("dependencies")) {
+                    mergeDependencies(parent.getDependencies());
+                }
+
+                if (extendTypes.contains("description")) {
+                    mergeDescription(parent.getDescription());
+                }
+            }
+
+        }
+
+        protected void mergeAll(ModuleDescriptor parent) {
+            ModuleRevisionId sourceMrid = parent.getModuleRevisionId();
+            mergeInfo(parent);
+            mergeConfigurations(sourceMrid, parent.getConfigurations());
+            mergeDependencies(parent.getDependencies());
+            mergeDescription(parent.getDescription());
+        }
+
+        protected void mergeInfo(ModuleDescriptor parent) {
+            ModuleRevisionId parentMrid = parent.getModuleRevisionId();
+
+            DefaultModuleDescriptor descriptor = getMd();
+            ModuleRevisionId currentMrid = descriptor.getModuleRevisionId();
+
+            ModuleRevisionId mergedMrid = ModuleRevisionId.newInstance(
+                mergeValue(parentMrid.getOrganisation(), currentMrid.getOrganisation()),
+                currentMrid.getName(),
+                mergeValue(parentMrid.getBranch(), currentMrid.getBranch()),
+                mergeValue(parentMrid.getRevision(), currentMrid.getRevision()),
+                mergeValues(parentMrid.getQualifiedExtraAttributes(),
+                            currentMrid.getQualifiedExtraAttributes())
+            );
+
+            descriptor.setModuleRevisionId(mergedMrid);
+            descriptor.setResolvedModuleRevisionId(mergedMrid);
+
+            descriptor.setStatus(mergeValue(parent.getStatus(), descriptor.getStatus()));
+            if (descriptor.getNamespace() == null && parent instanceof DefaultModuleDescriptor) {
+                Namespace parentNamespace = ((DefaultModuleDescriptor) parent).getNamespace();
+                descriptor.setNamespace(parentNamespace);
+            }
+        }
+
+        private static String mergeValue(String inherited, String override) {
+            return override == null ? inherited : override;
+        }
+
+        private static Map mergeValues(Map inherited, Map overrides) {
+            LinkedHashMap dup = new LinkedHashMap(inherited.size() + overrides.size());
+            dup.putAll(inherited);
+            dup.putAll(overrides);
+            return dup;
+        }
+
+        protected void mergeConfigurations(ModuleRevisionId sourceMrid, Configuration[] configurations) {
+            DefaultModuleDescriptor md = getMd();
+            for (int i = 0; i < configurations.length; i++) {
+                Configuration configuration = configurations[i];
+                Message.debug("Merging configuration with: " + configuration.getName());
+                //copy configuration from parent descriptor
+                md.addConfiguration(new Configuration(configuration, sourceMrid));
+            }
+        }
+
+        protected void mergeDependencies(DependencyDescriptor[] dependencies) {
+            DefaultModuleDescriptor md = getMd();
+            for (int i = 0; i < dependencies.length; i++) {
+                DependencyDescriptor dependencyDescriptor = dependencies[i];
+                Message.debug("Merging dependency with: "
+                        + dependencyDescriptor.getDependencyRevisionId().toString());
+                md.addDependency(dependencyDescriptor);
+            }
+        }
+
+        protected void mergeDescription(String description) {
+            String current = getMd().getDescription();
+            if (current == null || current.trim().length() == 0) {
+                getMd().setDescription(description);
+            }
+        }
+
+        protected ModuleDescriptor parseOtherIvyFileOnFileSystem(String location)
+                throws ParseException, IOException {
+            URL url = null;
+            ModuleDescriptor parent = null;
+            url = getSettings().getRelativeUrlResolver().getURL(descriptorURL, location);
+            Message.debug("Trying to load included ivy file from " + url.toString());
+            URLResource res = new URLResource(url);
+            ModuleDescriptorParser parser = ModuleDescriptorParserRegistry.getInstance().getParser(
+                res);
+
+            parent = parser.parseDescriptor(getSettings(), url, isValidate());
+            return parent;
+        }
+
+        protected ModuleDescriptor parseOtherIvyFile(String parentOrganisation,
+                String parentModule, String parentRevision) throws ParseException {
+            ModuleId parentModuleId = new ModuleId(parentOrganisation, parentModule);
+            ModuleRevisionId parentMrid = new ModuleRevisionId(parentModuleId, parentRevision);
+
+            // try to load parent module in cache
+            File cacheFile = settings.getResolutionCacheManager().getResolvedIvyFileInCache(
+                ModuleRevisionId.newInstance(parentMrid, Ivy.getWorkingRevision()));
+            if (cacheFile.exists() && cacheFile.length() > 0) {
+                ModuleDescriptor md;
+                try {
+                    Message.debug("Trying to load included ivy file from cache");
+                    URL parentUrl = cacheFile.toURI().toURL();
+                    md = parseOtherIvyFileOnFileSystem(parentUrl.toString());
+                    return md;
+                } catch (IOException e) {
+                    // do nothing
+                    Message.error(e.getLocalizedMessage());
+                }
+            }
+
+            DependencyDescriptor dd = new DefaultDependencyDescriptor(parentMrid, true);
+            ResolveData data = IvyContext.getContext().getResolveData();
+            if (data == null) {
+                ResolveEngine engine = IvyContext.getContext().getIvy().getResolveEngine();
+                ResolveOptions options = new ResolveOptions();
+                options.setDownload(false);
+                data = new ResolveData(engine, options);
+            }
+
+            DependencyResolver resolver = getSettings().getResolver(parentMrid);
+            if (resolver == null) {
+                // TODO: Throw exception here?
+                return null;
+            } else {
+                dd = NameSpaceHelper.toSystem(dd, getSettings().getContextNamespace());
+                ResolvedModuleRevision otherModule = resolver.getDependency(dd, data);
+                if (otherModule == null) {
+                    throw new ParseException("Unable to find " + parentMrid.toString(), 0);
+                }
+                return otherModule.getDescriptor();
+            }
+
+        }
+
+        protected void publicationsStarted(Attributes attributes) {
+            state = State.PUB;
+            artifactsDeclared = true;
+            checkConfigurations();
+            String defaultConf = settings.substitute(attributes.getValue("defaultconf"));
+            if (defaultConf != null) {
+                setPublicationsDefaultConf(defaultConf);
+            }
+        }
+
+        protected void setPublicationsDefaultConf(String defaultConf) {
+            this.publicationsDefaultConf = defaultConf == null ? null : defaultConf.split(",");
+        }
+
+        protected boolean isOtherNamespace(String qName) {
+            return qName.indexOf(':') != -1;
+        }
+
+        protected void managerStarted(Attributes attributes, String managerAtt) {
+            String org = settings.substitute(attributes.getValue("org"));
+            org = org == null ? PatternMatcher.ANY_EXPRESSION : org;
+            String mod = settings.substitute(attributes.getValue("module"));
+            mod = mod == null ? PatternMatcher.ANY_EXPRESSION : mod;
+            ConflictManager cm;
+            String name = settings.substitute(attributes.getValue(managerAtt));
+            String rev = settings.substitute(attributes.getValue("rev"));
+            if (rev != null) {
+                String[] revs = rev.split(",");
+                for (int i = 0; i < revs.length; i++) {
+                    revs[i] = revs[i].trim();
+                }
+                cm = new FixedConflictManager(revs);
+            } else if (name != null) {
+                cm = settings.getConflictManager(name);
+                if (cm == null) {
+                    addError("unknown conflict manager: " + name);
+                    return;
+                }
+            } else {
+                addError("bad conflict manager: no manager nor rev");
+                return;
+            }
+            String matcherName = settings.substitute(attributes.getValue("matcher"));
+            PatternMatcher matcher = matcherName == null ? defaultMatcher : settings
+                    .getMatcher(matcherName);
+            if (matcher == null) {
+                addError("unknown matcher: " + matcherName);
+                return;
+            }
+            getMd().addConflictManager(new ModuleId(org, mod), matcher, cm);
+        }
+
+        protected void mediationOverrideStarted(Attributes attributes) {
+            String org = settings.substitute(attributes.getValue("org"));
+            org = org == null ? PatternMatcher.ANY_EXPRESSION : org;
+            String mod = settings.substitute(attributes.getValue("module"));
+            mod = mod == null ? PatternMatcher.ANY_EXPRESSION : mod;
+            String rev = settings.substitute(attributes.getValue("rev"));
+            String branch = settings.substitute(attributes.getValue("branch"));
+            String matcherName = settings.substitute(attributes.getValue("matcher"));
+            PatternMatcher matcher = matcherName == null ? defaultMatcher : settings
+                    .getMatcher(matcherName);
+            if (matcher == null) {
+                addError("unknown matcher: " + matcherName);
+                return;
+            }
+            getMd().addDependencyDescriptorMediator(
+                new ModuleId(org, mod), matcher,
+                new OverrideDependencyDescriptorMediator(branch, rev));
+        }
+
+        protected void includeConfStarted(Attributes attributes)
+                throws SAXException, IOException, ParserConfigurationException, ParseException {
+            URL url = settings.getRelativeUrlResolver().getURL(descriptorURL,
+                    settings.substitute(attributes.getValue("file")),
+                    settings.substitute(attributes.getValue("url")));
+
+            if (url == null) {
+                throw new SAXException("include tag must have a file or an url attribute");
+            }
+
+            // create a new temporary parser to read the configurations from
+            // the specified file.
+            Parser parser = new Parser(getModuleDescriptorParser(), settings);
+            parser.setInput(url);
+            parser.setMd(new DefaultModuleDescriptor(getModuleDescriptorParser(),
+                    new URLResource(url)));
+            XMLHelper.parse(url , null, parser);
+
+            // add the configurations from this temporary parser to this module descriptor
+            Configuration[] configs = parser.getModuleDescriptor().getConfigurations();
+            for (int i = 0; i < configs.length; i++) {
+                getMd().addConfiguration(configs[i]);
+            }
+            if (parser.getDefaultConfMapping() != null) {
+                Message.debug("setting default conf mapping from imported configurations file: "
+                        + parser.getDefaultConfMapping());
+                setDefaultConfMapping(parser.getDefaultConfMapping());
+            }
+            if (parser.getDefaultConf() != null) {
+                Message.debug("setting default conf from imported configurations file: "
+                        + parser.getDefaultConf());
+                setDefaultConf(parser.getDefaultConf());
+            }
+            if (parser.getMd().isMappingOverride()) {
+                Message.debug("enabling mapping-override from imported configurations"
+                        + " file");
+                getMd().setMappingOverride(true);
+            }
+        }
+
+        protected void confStarted(Attributes attributes) {
+            String conf = settings.substitute(attributes.getValue("name"));
+            switch (state) {
+                case State.CONF:
+                    String visibility = settings.substitute(attributes.getValue("visibility"));
+                    String ext = settings.substitute(attributes.getValue("extends"));
+                    String transitiveValue = attributes.getValue("transitive");
+                    boolean transitive = (transitiveValue == null) ? true : Boolean
+                            .valueOf(attributes.getValue("transitive")).booleanValue();
+                    String deprecated = attributes.getValue("deprecated");
+                    Configuration configuration = new Configuration(conf,
+                            Configuration.Visibility
+                                    .getVisibility(visibility == null ? "public"
+                                            : visibility), settings.substitute(attributes
+                                    .getValue("description")), ext == null ? null : ext
+                                    .split(","), transitive, deprecated);
+                    ExtendableItemHelper.fillExtraAttributes(settings, configuration, attributes,
+                            new String[]{"name", "visibility", "extends", "transitive",
+                                    "description", "deprecated"});
+                    getMd().addConfiguration(configuration);
+                    break;
+                case State.PUB:
+                    if ("*".equals(conf)) {
+                        String[] confs = getMd().getConfigurationsNames();
+                        for (int i = 0; i < confs.length; i++) {
+                            artifact.addConfiguration(confs[i]);
+                            getMd().addArtifact(confs[i], artifact);
+                        }
+                    } else {
+                        artifact.addConfiguration(conf);
+                        getMd().addArtifact(conf, artifact);
+                    }
+                    break;
+                case State.DEP:
+                    this.conf = conf;
+                    String mappeds = settings.substitute(attributes.getValue("mapped"));
+                    if (mappeds != null) {
+                        String[] mapped = mappeds.split(",");
+                        for (int i = 0; i < mapped.length; i++) {
+                            dd.addDependencyConfiguration(conf, mapped[i].trim());
+                        }
+                    }
+                    break;
+                case State.DEP_ARTIFACT:
+                case State.ARTIFACT_INCLUDE:
+                case State.ARTIFACT_EXCLUDE:
+                    addConfiguration(conf);
+                    break;
+                default:
+                    if (validate) {
+                        addError("conf tag found in invalid tag: " + state);
+                    }
+                    break;
+            }
+        }
+
+        protected void dependencyStarted(Attributes attributes) {
+            state = State.DEP;
+            String org = settings.substitute(attributes.getValue("org"));
+            if (org == null) {
+                org = getMd().getModuleRevisionId().getOrganisation();
+            }
+            boolean force = Boolean.valueOf(settings.substitute(attributes.getValue("force")))
+                    .booleanValue();
+            boolean changing = Boolean.valueOf(
+                settings.substitute(attributes.getValue("changing"))).booleanValue();
+
+            String transitiveValue = settings.substitute(attributes.getValue("transitive"));
+            boolean transitive = (transitiveValue == null) ? true : Boolean.valueOf(
+                attributes.getValue("transitive")).booleanValue();
+
+            String name = settings.substitute(attributes.getValue("name"));
+            String branch = settings.substitute(attributes.getValue("branch"));
+            String branchConstraint = settings.substitute(attributes.getValue("branchConstraint"));
+
+//            if (branchConstraint == null) {
+//                // there was no branch constraint before, so we should
+//                // set the branchConstraint to the current default branch
+//                branchConstraint = settings.getDefaultBranch(ModuleId.newInstance(org, name));
+//            }
+
+            String rev = settings.substitute(attributes.getValue("rev"));
+            String revConstraint = settings.substitute(attributes.getValue("revConstraint"));
+
+            Map extraAttributes = ExtendableItemHelper.getExtraAttributes(
+                settings, attributes, DEPENDENCY_REGULAR_ATTRIBUTES);
+
+            ModuleRevisionId revId = ModuleRevisionId.newInstance(org, name, branch, rev,
+                extraAttributes);
+            ModuleRevisionId dynamicId = null;
+            if ((revConstraint == null) && (branchConstraint == null)) {
+                // no dynamic constraints defined, so dynamicId equals revId
+                dynamicId = ModuleRevisionId.newInstance(org, name, branch, rev,
+                                extraAttributes, false);
+            } else {
+                if (branchConstraint == null) {
+                    // this situation occurs when there was no branch defined
+                    // in the original dependency descriptor. So the dynamicId
+                    // shouldn't contain a branch neither
+                    dynamicId = ModuleRevisionId.newInstance(org, name, null, revConstraint,
+                                    extraAttributes, false);
+                } else {
+                    dynamicId = ModuleRevisionId.newInstance(org, name, branchConstraint,
+                                    revConstraint, extraAttributes);
+                }
+            }
+
+            dd = new DefaultDependencyDescriptor(getMd(), revId, dynamicId, force, changing, transitive);
+            getMd().addDependency(dd);
+            String confs = settings.substitute(attributes.getValue("conf"));
+            if (confs != null && confs.length() > 0) {
+                parseDepsConfs(confs, dd);
+            }
+        }
+
+        protected void artifactStarted(String qName, Attributes attributes)
+                throws MalformedURLException {
+            if (state == State.PUB) {
+                // this is a published artifact
+                String artName = settings.substitute(attributes.getValue("name"));
+                artName = artName == null ? getMd().getModuleRevisionId().getName() : artName;
+                String type = settings.substitute(attributes.getValue("type"));
+                type = type == null ? "jar" : type;
+                String ext = settings.substitute(attributes.getValue("ext"));
+                ext = ext != null ? ext : type;
+                String url = settings.substitute(attributes.getValue("url"));
+                artifact = new MDArtifact(getMd(), artName, type, ext, url == null ? null
+                        : new URL(url), ExtendableItemHelper.getExtraAttributes(
+                            settings, attributes, new String[] {"ext", "type", "name", "conf"}));
+                String confs = settings.substitute(attributes.getValue("conf"));
+                // only add confs if they are specified. if they aren't, endElement will
+                // handle this
+                // only if there are no conf defined in sub elements
+                if (confs != null && confs.length() > 0) {
+                    String[] conf;
+                    if ("*".equals(confs)) {
+                        conf = getMd().getConfigurationsNames();
+                    } else {
+                        conf = confs.split(",");
+                    }
+                    for (int i = 0; i < conf.length; i++) {
+                        artifact.addConfiguration(conf[i].trim());
+                        getMd().addArtifact(conf[i].trim(), artifact);
+                    }
+                }
+            } else if (state == State.DEP) {
+                // this is an artifact asked for a particular dependency
+                addDependencyArtifacts(qName, attributes);
+            } else if (validate) {
+                addError("artifact tag found in invalid tag: " + state);
+            }
+        }
+
+        protected void dependenciesStarted(Attributes attributes) {
+            state = State.DEPS;
+            String defaultConf = settings.substitute(attributes.getValue("defaultconf"));
+            if (defaultConf != null) {
+                setDefaultConf(defaultConf);
+            }
+            defaultConf = settings.substitute(attributes.getValue("defaultconfmapping"));
+            if (defaultConf != null) {
+                setDefaultConfMapping(defaultConf);
+            }
+            String confMappingOverride = settings.substitute(attributes
+                    .getValue("confmappingoverride"));
+            if (confMappingOverride != null) {
+                getMd().setMappingOverride(Boolean.valueOf(confMappingOverride).booleanValue());
+            }
+            checkConfigurations();
+        }
+
+        protected void configurationStarted(Attributes attributes) {
+            state = State.CONF;
+            setDefaultConfMapping(settings
+                    .substitute(attributes.getValue("defaultconfmapping")));
+            setDefaultConf(settings.substitute(attributes.getValue("defaultconf")));
+            getMd()
+                    .setMappingOverride(Boolean.valueOf(
+                        settings.substitute(attributes.getValue("confmappingoverride")))
+                            .booleanValue());
+        }
+
+        protected void infoStarted(Attributes attributes) {
+            state = State.INFO;
+            String org = settings.substitute(attributes.getValue("organisation"));
+            String module = settings.substitute(attributes.getValue("module"));
+            String revision = settings.substitute(attributes.getValue("revision"));
+            String branch = settings.substitute(attributes.getValue("branch"));
+            getMd().setModuleRevisionId(ModuleRevisionId.newInstance(org, module, branch,
+                revision, ExtendableItemHelper.getExtraAttributes(settings, attributes,
+                    new String[] {
+                        "organisation", "module", "revision", "status", "publication",
+                        "branch", "namespace", "default", "resolver"})));
+
+            String namespace = settings.substitute(attributes.getValue("namespace"));
+            if (namespace != null) {
+                Namespace ns = settings.getNamespace(namespace);
+                if (ns == null) {
+                    Message.warn("namespace not found for " + getMd().getModuleRevisionId()
+                            + ": " + namespace);
+                } else {
+                    getMd().setNamespace(ns);
+                }
+            }
+
+            String status = settings.substitute(attributes.getValue("status"));
+            getMd().setStatus(status == null ? settings.getStatusManager().getDefaultStatus()
+                    : status);
+            getMd().setDefault(Boolean.valueOf(settings.substitute(attributes.getValue("default")))
+                    .booleanValue());
+            String pubDate = settings.substitute(attributes.getValue("publication"));
+            if (pubDate != null && pubDate.length() > 0) {
+                try {
+                    final SimpleDateFormat ivyDateFormat = new SimpleDateFormat(IVY_DATE_FORMAT_PATTERN);
+                    getMd().setPublicationDate(ivyDateFormat.parse(pubDate));
+                } catch (ParseException e) {
+                    addError("invalid publication date format: " + pubDate);
+                    getMd().setPublicationDate(getDefaultPubDate());
+                }
+            } else {
+                getMd().setPublicationDate(getDefaultPubDate());
+            }
+        }
+
+        protected void ivyModuleStarted(Attributes attributes) throws SAXException {
+            descriptorVersion = attributes.getValue("version");
+            int versionIndex = ALLOWED_VERSIONS.indexOf(descriptorVersion);
+            if (versionIndex == -1) {
+                addError("invalid version " + descriptorVersion);
+                throw new SAXException("invalid version " + descriptorVersion);
+            }
+            if (versionIndex >= ALLOWED_VERSIONS.indexOf("1.3")) {
+                Message.debug("post 1.3 ivy file: using " + PatternMatcher.EXACT
+                        + " as default matcher");
+                defaultMatcher = settings.getMatcher(PatternMatcher.EXACT);
+            } else {
+                Message.debug("pre 1.3 ivy file: using " + PatternMatcher.EXACT_OR_REGEXP
+                        + " as default matcher");
+                defaultMatcher = settings.getMatcher(PatternMatcher.EXACT_OR_REGEXP);
+            }
+
+            for (int i = 0; i < attributes.getLength(); i++) {
+                if (attributes.getQName(i).startsWith("xmlns:")) {
+                    getMd().addExtraAttributeNamespace(
+                        attributes.getQName(i).substring("xmlns:".length()),
+                        attributes.getValue(i));
+                }
+            }
+        }
+
+        protected void addDependencyArtifacts(String tag, Attributes attributes)
+                throws MalformedURLException {
+            state = State.DEP_ARTIFACT;
+            parseRule(tag, attributes);
+        }
+
+        protected void addIncludeRule(String tag, Attributes attributes)
+                throws MalformedURLException {
+            state = State.ARTIFACT_INCLUDE;
+            parseRule(tag, attributes);
+        }
+
+        protected void addExcludeRule(String tag, Attributes attributes)
+                throws MalformedURLException {
+            state = State.ARTIFACT_EXCLUDE;
+            parseRule(tag, attributes);
+        }
+
+        protected void parseRule(String tag, Attributes attributes) throws MalformedURLException {
+            String name = settings.substitute(attributes.getValue("name"));
+            if (name == null) {
+                name = settings.substitute(attributes.getValue("artifact"));
+                if (name == null) {
+                    name = "artifact".equals(tag) ? dd.getDependencyId().getName()
+                            : PatternMatcher.ANY_EXPRESSION;
+                }
+            }
+            String type = settings.substitute(attributes.getValue("type"));
+            if (type == null) {
+                type = "artifact".equals(tag) ? "jar" : PatternMatcher.ANY_EXPRESSION;
+            }
+            String ext = settings.substitute(attributes.getValue("ext"));
+            ext = ext != null ? ext : type;
+            if (state == State.DEP_ARTIFACT) {
+                String url = settings.substitute(attributes.getValue("url"));
+                Map extraAtt = ExtendableItemHelper.getExtraAttributes(settings, attributes,
+                    new String[] {"name", "type", "ext", "url", "conf"});
+                confAware = new DefaultDependencyArtifactDescriptor(dd, name, type, ext,
+                        url == null ? null : new URL(url), extraAtt);
+            } else if (state == State.ARTIFACT_INCLUDE) {
+                PatternMatcher matcher = getPatternMatcher(attributes.getValue("matcher"));
+                String org = settings.substitute(attributes.getValue("org"));
+                org = org == null ? PatternMatcher.ANY_EXPRESSION : org;
+                String module = settings.substitute(attributes.getValue("module"));
+                module = module == null ? PatternMatcher.ANY_EXPRESSION : module;
+                ArtifactId aid = new ArtifactId(new ModuleId(org, module), name, type, ext);
+                Map extraAtt = ExtendableItemHelper.getExtraAttributes(settings, attributes,
+                    new String[] {"org", "module", "name", "type", "ext", "matcher", "conf"});
+                confAware = new DefaultIncludeRule(aid, matcher, extraAtt);
+            } else { // _state == ARTIFACT_EXCLUDE || EXCLUDE
+                PatternMatcher matcher = getPatternMatcher(attributes.getValue("matcher"));
+                String org = settings.substitute(attributes.getValue("org"));
+                org = org == null ? PatternMatcher.ANY_EXPRESSION : org;
+                String module = settings.substitute(attributes.getValue("module"));
+                module = module == null ? PatternMatcher.ANY_EXPRESSION : module;
+                ArtifactId aid = new ArtifactId(new ModuleId(org, module), name, type, ext);
+                Map extraAtt = ExtendableItemHelper.getExtraAttributes(settings, attributes,
+                    new String[] {"org", "module", "name", "type", "ext", "matcher", "conf"});
+                confAware = new DefaultExcludeRule(aid, matcher, extraAtt);
+            }
+            String confs = settings.substitute(attributes.getValue("conf"));
+            // only add confs if they are specified. if they aren't, endElement will handle this
+            // only if there are no conf defined in sub elements
+            if (confs != null && confs.length() > 0) {
+                String[] conf;
+                if ("*".equals(confs)) {
+                    conf = getMd().getConfigurationsNames();
+                } else {
+                    conf = confs.split(",");
+                }
+                for (int i = 0; i < conf.length; i++) {
+                    addConfiguration(conf[i].trim());
+                }
+            }
+        }
+
+        protected void addConfiguration(String c) {
+            confAware.addConfiguration(c);
+            if (state != State.EXCLUDE) {
+                // we are currently adding a configuration to either an include, exclude or artifact
+                // element
+                // of a dependency. This means that we have to add this element to the corresponding
+                // conf
+                // of the current dependency descriptor
+                if (confAware instanceof DependencyArtifactDescriptor) {
+                    dd.addDependencyArtifact(c, (DependencyArtifactDescriptor) confAware);
+                } else if (confAware instanceof IncludeRule) {
+                    dd.addIncludeRule(c, (IncludeRule) confAware);
+                } else if (confAware instanceof ExcludeRule) {
+                    dd.addExcludeRule(c, (ExcludeRule) confAware);
+                }
+            }
+        }
+
+        protected PatternMatcher getPatternMatcher(String m) {
+            String matcherName = settings.substitute(m);
+            PatternMatcher matcher = matcherName == null ? defaultMatcher : settings
+                    .getMatcher(matcherName);
+            if (matcher == null) {
+                throw new IllegalArgumentException("unknown matcher " + matcherName);
+            }
+            return matcher;
+        }
+
+
+        public void characters(char[] ch, int start, int length) throws SAXException {
+            if (buffer != null) {
+                buffer.append(ch, start, length);
+            }
+        }
+
+
+        public void endElement(String uri, String localName, String qName) throws SAXException {
+            if (state == State.PUB && "artifact".equals(qName)
+                    && artifact.getConfigurations().length == 0) {
+                String[] confs = publicationsDefaultConf == null
+                    ? getMd().getConfigurationsNames()
+                    : publicationsDefaultConf;
+                for (int i = 0; i < confs.length; i++) {
+                    artifact.addConfiguration(confs[i].trim());
+                    getMd().addArtifact(confs[i].trim(), artifact);
+                }
+            } else if ("configurations".equals(qName)) {
+                checkConfigurations();
+            } else if ((state == State.DEP_ARTIFACT && "artifact".equals(qName))
+                    || (state == State.ARTIFACT_INCLUDE && "include".equals(qName))
+                    || (state == State.ARTIFACT_EXCLUDE && "exclude".equals(qName))) {
+                state = State.DEP;
+                if (confAware.getConfigurations().length == 0) {
+                    String[] confs = getMd().getConfigurationsNames();
+                    for (int i = 0; i < confs.length; i++) {
+                        addConfiguration(confs[i]);
+                    }
+                }
+                confAware = null;
+            } else if ("exclude".equals(qName) && state == State.EXCLUDE) {
+                if (confAware.getConfigurations().length == 0) {
+                    String[] confs = getMd().getConfigurationsNames();
+                    for (int i = 0; i < confs.length; i++) {
+                        addConfiguration(confs[i]);
+                    }
+                }
+                confAware = null;
+                state = State.DEPS;
+            } else if ("dependency".equals(qName) && state == State.DEP) {
+                if (dd.getModuleConfigurations().length == 0) {
+                    parseDepsConfs(getDefaultConf(), dd);
+                }
+                state = State.DEPS;
+            } else if ("dependencies".equals(qName) && state == State.DEPS) {
+                state = State.NONE;
+            } else if (state == State.INFO && "info".equals(qName)) {
+                state = State.NONE;
+            } else if (state == State.DESCRIPTION && "description".equals(qName)) {
+                getMd().setDescription(buffer == null ? "" : buffer.toString().trim());
+                buffer = null;
+                state = State.INFO;
+            } else if (state == State.EXTRA_INFO) {
+                getMd().addExtraInfo(qName, buffer == null ? "" : buffer.toString());
+                buffer = null;
+                state = State.INFO;
+            } else if (state == State.DESCRIPTION) {
+                if (buffer.toString().endsWith("<" + qName + ">")) {
+                    buffer.deleteCharAt(buffer.length() - 1);
+                    buffer.append("/>");
+                } else {
+                    buffer.append("</" + qName + ">");
+                }
+            }
+        }
+
+        protected void checkConfigurations() {
+            if (getMd().getConfigurations().length == 0) {
+                getMd().addConfiguration(new Configuration("default"));
+            }
+        }
+
+        protected void replaceConfigurationWildcards() {
+            Configuration[] configs = getMd().getConfigurations();
+            for (int i = 0; i < configs.length; i++) {
+                configs[i].replaceWildcards(getMd());
+            }
+        }
+
+        /* getters and setters available for extension only */
+        protected ParserSettings getSettings() {
+            return settings;
+        }
+
+        protected URL getDescriptorURL() {
+            return descriptorURL;
+        }
+
+        protected InputStream getDescriptorInput() {
+            return descriptorInput;
+        }
+
+        protected int getState() {
+            return state;
+        }
+
+        protected void setState(int state) {
+            this.state = state;
+        }
+
+        protected PatternMatcher getDefaultMatcher() {
+            return defaultMatcher;
+        }
+
+        protected DefaultDependencyDescriptor getDd() {
+            return dd;
+        }
+
+        protected void setDd(DefaultDependencyDescriptor dd) {
+            this.dd = dd;
+        }
+
+        protected ConfigurationAware getConfAware() {
+            return confAware;
+        }
+
+        protected void setConfAware(ConfigurationAware confAware) {
+            this.confAware = confAware;
+        }
+
+        protected MDArtifact getArtifact() {
+            return artifact;
+        }
+
+        protected void setArtifact(MDArtifact artifact) {
+            this.artifact = artifact;
+        }
+
+        protected String getConf() {
+            return conf;
+        }
+
+        protected void setConf(String conf) {
+            this.conf = conf;
+        }
+
+        protected boolean isArtifactsDeclared() {
+            return artifactsDeclared;
+        }
+
+        protected void setArtifactsDeclared(boolean artifactsDeclared) {
+            this.artifactsDeclared = artifactsDeclared;
+        }
+
+        protected StringBuffer getBuffer() {
+            return buffer;
+        }
+
+        protected void setBuffer(StringBuffer buffer) {
+            this.buffer = buffer;
+        }
+
+        protected String getDescriptorVersion() {
+            return descriptorVersion;
+        }
+
+        protected void setDescriptorVersion(String descriptorVersion) {
+            this.descriptorVersion = descriptorVersion;
+        }
+
+        protected String[] getPublicationsDefaultConf() {
+            return publicationsDefaultConf;
+        }
+
+        protected void setPublicationsDefaultConf(String[] publicationsDefaultConf) {
+            this.publicationsDefaultConf = publicationsDefaultConf;
+        }
+
+        protected boolean isValidate() {
+            return validate;
+        }
+
+        protected URL getSchemaURL() {
+            return getClass().getResource("ivy.xsd");
+        }
+    }
+
+    public String toString() {
+        return "ivy parser";
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ModuleScopedParserSettings.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ModuleScopedParserSettings.java
index 6a67ff8..82905c7 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ModuleScopedParserSettings.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ModuleScopedParserSettings.java
@@ -45,6 +45,10 @@ public class ModuleScopedParserSettings implements ParserSettings {
         this.currentRevisionId = currentRevisionId;
     }
 
+    public ModuleRevisionId getCurrentRevisionId() {
+        return currentRevisionId;
+    }
+
     public DependencyResolver getResolver(ModuleRevisionId mRevId) {
         if (mRevId.equals(currentRevisionId)) {
             return currentResolver;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultCachedModuleDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultCachedModuleDescriptor.java
index e16c5d0..6596c0e 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultCachedModuleDescriptor.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultCachedModuleDescriptor.java
@@ -19,25 +19,34 @@ import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.artifacts.ResolvedModuleVersion;
 import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.DefaultResolvedModuleVersion;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
 import org.gradle.internal.TimeProvider;
 
-import java.io.Serializable;
+import java.math.BigInteger;
 
-class DefaultCachedModuleDescriptor implements ModuleDescriptorCache.CachedModuleDescriptor, Serializable {
+class DefaultCachedModuleDescriptor implements ModuleDescriptorCache.CachedModuleDescriptor {
     private final ModuleDescriptor moduleDescriptor;
+    private final ModuleSource moduleSource;
+    private final BigInteger descriptorHash;
     private final boolean isChangingModule;
     private final long ageMillis;
 
     public DefaultCachedModuleDescriptor(ModuleDescriptorCacheEntry entry, ModuleDescriptor moduleDescriptor, TimeProvider timeProvider) {
         this.moduleDescriptor = moduleDescriptor;
+        this.moduleSource = entry.moduleSource;
         this.isChangingModule = entry.isChanging;
-        ageMillis = timeProvider.getCurrentTime() - entry.createTimestamp;
+        this.descriptorHash = entry.moduleDescriptorHash;
+        this.ageMillis = timeProvider.getCurrentTime() - entry.createTimestamp;
     }
 
     public boolean isMissing() {
         return moduleDescriptor == null;
     }
 
+    public ModuleSource getModuleSource() {
+        return moduleSource;
+    }
+
     public ResolvedModuleVersion getModuleVersion() {
         ModuleRevisionId moduleRevisionId = isMissing() ? null : moduleDescriptor.getModuleRevisionId();
         return new DefaultResolvedModuleVersion(moduleRevisionId);
@@ -54,4 +63,8 @@ class DefaultCachedModuleDescriptor implements ModuleDescriptorCache.CachedModul
     public long getAgeMillis() {
         return ageMillis;
     }
+
+    public BigInteger getDescriptorHash() {
+        return descriptorHash;
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleDescriptorCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleDescriptorCache.java
index 523d029..e220920 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleDescriptorCache.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleDescriptorCache.java
@@ -16,20 +16,26 @@
 package org.gradle.api.internal.artifacts.ivyservice.modulecache;
 
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ModuleVersionIdentifierSerializer;
 import org.gradle.api.internal.artifacts.ivyservice.ArtifactCacheMetaData;
 import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
 import org.gradle.api.internal.artifacts.ivyservice.IvyXmlModuleDescriptorWriter;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.IvyXmlModuleDescriptorParser;
+import org.gradle.api.internal.filestore.FileStoreEntry;
 import org.gradle.api.internal.filestore.PathKeyFileStore;
 import org.gradle.cache.PersistentIndexedCache;
 import org.gradle.internal.TimeProvider;
+import org.gradle.messaging.serialize.DataStreamBackedSerializer;
+import org.gradle.messaging.serialize.DefaultSerializer;
+import org.gradle.util.hash.HashValue;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
-import java.io.Serializable;
+import java.io.*;
+import java.math.BigInteger;
 
 public class DefaultModuleDescriptorCache implements ModuleDescriptorCache {
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultModuleDescriptorCache.class);
@@ -47,7 +53,7 @@ public class DefaultModuleDescriptorCache implements ModuleDescriptorCache {
         this.cacheMetadata = cacheMetadata;
 
         // TODO:DAZ inject this
-        moduleDescriptorStore = new ModuleDescriptorStore(new PathKeyFileStore(cacheMetadata.getCacheDir()), new IvyXmlModuleDescriptorWriter(), XmlModuleDescriptorParser.getInstance());
+        moduleDescriptorStore = new ModuleDescriptorStore(new PathKeyFileStore(cacheMetadata.getCacheDir()), new IvyXmlModuleDescriptorWriter(), new IvyXmlModuleDescriptorParser());
     }
 
     private PersistentIndexedCache<RevisionKey, ModuleDescriptorCacheEntry> getCache() {
@@ -59,18 +65,18 @@ public class DefaultModuleDescriptorCache implements ModuleDescriptorCache {
 
     private PersistentIndexedCache<RevisionKey, ModuleDescriptorCacheEntry> initCache() {
         File artifactResolutionCacheFile = new File(cacheMetadata.getCacheDir(), "module-metadata.bin");
-        return cacheLockingManager.createCache(artifactResolutionCacheFile, RevisionKey.class, ModuleDescriptorCacheEntry.class);
+        return cacheLockingManager.createCache(artifactResolutionCacheFile, new RevisionKeySerializer(), new ModuleDescriptorCacheEntrySerializer());
     }
 
-    public CachedModuleDescriptor getCachedModuleDescriptor(ModuleVersionRepository repository, ModuleRevisionId moduleRevisionId) {
-        ModuleDescriptorCacheEntry moduleDescriptorCacheEntry = getCache().get(createKey(repository, moduleRevisionId));
+    public CachedModuleDescriptor getCachedModuleDescriptor(ModuleVersionRepository repository, ModuleVersionIdentifier moduleVersionIdentifier) {
+        ModuleDescriptorCacheEntry moduleDescriptorCacheEntry = getCache().get(createKey(repository, moduleVersionIdentifier));
         if (moduleDescriptorCacheEntry == null) {
             return null;
         }
         if (moduleDescriptorCacheEntry.isMissing) {
             return new DefaultCachedModuleDescriptor(moduleDescriptorCacheEntry, null, timeProvider);
         }
-        ModuleDescriptor descriptor = moduleDescriptorStore.getModuleDescriptor(repository, moduleRevisionId);
+        ModuleDescriptor descriptor = moduleDescriptorStore.getModuleDescriptor(repository, moduleVersionIdentifier);
         if (descriptor == null) {
             // Descriptor file has been manually deleted - ignore the entry
             return null;
@@ -78,36 +84,40 @@ public class DefaultModuleDescriptorCache implements ModuleDescriptorCache {
         return new DefaultCachedModuleDescriptor(moduleDescriptorCacheEntry, descriptor, timeProvider);
     }
 
-    public void cacheModuleDescriptor(ModuleVersionRepository repository, ModuleRevisionId moduleRevisionId, ModuleDescriptor moduleDescriptor, boolean isChanging) {
+    public CachedModuleDescriptor cacheModuleDescriptor(ModuleVersionRepository repository, ModuleVersionIdentifier moduleVersionIdentifier, ModuleDescriptor moduleDescriptor, ModuleSource moduleSource, boolean isChanging) {
+        ModuleDescriptorCacheEntry entry;
         if (moduleDescriptor == null) {
-            LOGGER.debug("Recording absence of module descriptor in cache: {} [changing = {}]", moduleRevisionId, isChanging);
-            getCache().put(createKey(repository, moduleRevisionId), createMissingEntry(isChanging));
+            LOGGER.debug("Recording absence of module descriptor in cache: {} [changing = {}]", moduleVersionIdentifier, isChanging);
+            entry = createMissingEntry(isChanging);
+            getCache().put(createKey(repository, moduleVersionIdentifier), entry);
         } else {
             LOGGER.debug("Recording module descriptor in cache: {} [changing = {}]", moduleDescriptor.getModuleRevisionId(), isChanging);
-            moduleDescriptorStore.putModuleDescriptor(repository, moduleDescriptor);
-            getCache().put(createKey(repository, moduleRevisionId), createEntry(isChanging));
+            FileStoreEntry fileStoreEntry = moduleDescriptorStore.putModuleDescriptor(repository, moduleDescriptor);
+            entry = createEntry(isChanging, fileStoreEntry.getSha1(), moduleSource);
+            getCache().put(createKey(repository, moduleVersionIdentifier), entry);
         }
+        return new DefaultCachedModuleDescriptor(entry, null, timeProvider);
     }
 
-    private RevisionKey createKey(ModuleVersionRepository resolver, ModuleRevisionId moduleRevisionId) {
-        return new RevisionKey(resolver, moduleRevisionId);
+    private RevisionKey createKey(ModuleVersionRepository repository, ModuleVersionIdentifier moduleVersionIdentifier) {
+        return new RevisionKey(repository.getId(), moduleVersionIdentifier);
     }
 
     private ModuleDescriptorCacheEntry createMissingEntry(boolean changing) {
-        return new ModuleDescriptorCacheEntry(changing, true, timeProvider);
+        return new ModuleDescriptorCacheEntry(changing, true, timeProvider.getCurrentTime(), BigInteger.ZERO, null);
     }
 
-    private ModuleDescriptorCacheEntry createEntry(boolean changing) {
-        return new ModuleDescriptorCacheEntry(changing, false, timeProvider);
+    private ModuleDescriptorCacheEntry createEntry(boolean changing, HashValue moduleDescriptorHash, ModuleSource moduleSource) {
+        return new ModuleDescriptorCacheEntry(changing, false, timeProvider.getCurrentTime(), moduleDescriptorHash.asBigInteger(), moduleSource);
     }
 
-    private static class RevisionKey implements Serializable {
-        private final String resolverId;
-        private final String moduleRevisionId;
+    private static class RevisionKey {
+        private final String repositoryId;
+        private final ModuleVersionIdentifier moduleVersionIdentifier;
 
-        private RevisionKey(ModuleVersionRepository repository, ModuleRevisionId moduleRevisionId) {
-            this.resolverId = repository.getId();
-            this.moduleRevisionId = moduleRevisionId == null ? null : moduleRevisionId.encodeToString();
+        private RevisionKey(String repositoryId, ModuleVersionIdentifier moduleVersionIdentifier) {
+            this.repositoryId = repositoryId;
+            this.moduleVersionIdentifier = moduleVersionIdentifier;
         }
 
         @Override
@@ -116,13 +126,64 @@ public class DefaultModuleDescriptorCache implements ModuleDescriptorCache {
                 return false;
             }
             RevisionKey other = (RevisionKey) o;
-            return resolverId.equals(other.resolverId) && moduleRevisionId.equals(other.moduleRevisionId);
+            return repositoryId.equals(other.repositoryId) && moduleVersionIdentifier.equals(other.moduleVersionIdentifier);
         }
 
         @Override
         public int hashCode() {
-            return resolverId.hashCode() ^ moduleRevisionId.hashCode();
+            return repositoryId.hashCode() ^ moduleVersionIdentifier.hashCode();
         }
     }
 
+    private static class RevisionKeySerializer extends DataStreamBackedSerializer<RevisionKey> {
+        private final ModuleVersionIdentifierSerializer identifierSerializer = new ModuleVersionIdentifierSerializer();
+
+        @Override
+        public void write(DataOutput dataOutput, RevisionKey value) throws IOException {
+            dataOutput.writeUTF(value.repositoryId);
+            identifierSerializer.write(dataOutput, value.moduleVersionIdentifier);
+        }
+
+        @Override
+        public RevisionKey read(DataInput dataInput) throws IOException {
+            String resolverId = dataInput.readUTF();
+            ModuleVersionIdentifier identifier = identifierSerializer.read(dataInput);
+            return new RevisionKey(resolverId, identifier);
+        }
+    }
+
+    private static class ModuleDescriptorCacheEntrySerializer extends DataStreamBackedSerializer<ModuleDescriptorCacheEntry> {
+        private final DefaultSerializer<ModuleSource> moduleSourceSerializer = new DefaultSerializer<ModuleSource>(ModuleSource.class.getClassLoader());
+
+        @Override
+        public void write(DataOutput dataOutput, ModuleDescriptorCacheEntry value) throws IOException {
+            dataOutput.writeBoolean(value.isMissing);
+            dataOutput.writeBoolean(value.isChanging);
+            dataOutput.writeLong(value.createTimestamp);
+            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            moduleSourceSerializer.write(outputStream, value.moduleSource);
+            byte[] serializedModuleSource = outputStream.toByteArray();
+            dataOutput.writeInt(serializedModuleSource.length);
+            dataOutput.write(serializedModuleSource);
+            byte[] hash = value.moduleDescriptorHash.toByteArray();
+            dataOutput.writeInt(hash.length);
+            dataOutput.write(hash);
+        }
+
+        @Override
+        public ModuleDescriptorCacheEntry read(DataInput dataInput) throws Exception {
+            boolean isMissing = dataInput.readBoolean();
+            boolean isChanging = dataInput.readBoolean();
+            long createTimestamp = dataInput.readLong();
+            int count = dataInput.readInt();
+            byte[] serializedModuleSource = new byte[count];
+            dataInput.readFully(serializedModuleSource);
+            ModuleSource moduleSource = moduleSourceSerializer.read(new ByteArrayInputStream(serializedModuleSource));
+            count = dataInput.readInt();
+            byte[] encodedHash = new byte[count];
+            dataInput.readFully(encodedHash);
+            BigInteger hash = new BigInteger(encodedHash);
+            return new ModuleDescriptorCacheEntry(isChanging, isMissing, createTimestamp, hash, moduleSource);
+        }
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCache.java
index 6e739c6..096535f 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCache.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCache.java
@@ -16,14 +16,17 @@
 package org.gradle.api.internal.artifacts.ivyservice.modulecache;
 
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.ResolvedModuleVersion;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
 
+import java.math.BigInteger;
+
 public interface ModuleDescriptorCache {
-    void cacheModuleDescriptor(ModuleVersionRepository repository, ModuleRevisionId resolvedModuleVersionId, ModuleDescriptor moduleDescriptor, boolean isChanging);
+    CachedModuleDescriptor cacheModuleDescriptor(ModuleVersionRepository repository, ModuleVersionIdentifier resolvedModuleVersionIdentifier, ModuleDescriptor moduleDescriptor, ModuleSource moduleSource, boolean isChanging);
 
-    CachedModuleDescriptor getCachedModuleDescriptor(ModuleVersionRepository repository, ModuleRevisionId moduleId);
+    CachedModuleDescriptor getCachedModuleDescriptor(ModuleVersionRepository repository, ModuleVersionIdentifier moduleVersionIdentifier);
 
     interface CachedModuleDescriptor {
         ResolvedModuleVersion getModuleVersion();
@@ -34,6 +37,10 @@ public interface ModuleDescriptorCache {
 
         long getAgeMillis();
 
+        BigInteger getDescriptorHash();
+
         boolean isMissing();
+
+        ModuleSource getModuleSource();
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCacheEntry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCacheEntry.java
index a26b8e8..461700c 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCacheEntry.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCacheEntry.java
@@ -15,18 +15,22 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.modulecache;
 
-import org.gradle.internal.TimeProvider;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
 
-import java.io.Serializable;
+import java.math.BigInteger;
 
-class ModuleDescriptorCacheEntry implements Serializable {
+class ModuleDescriptorCacheEntry {
     public boolean isChanging;
     public boolean isMissing;
     public long createTimestamp;
+    public ModuleSource moduleSource;
+    public BigInteger moduleDescriptorHash;
 
-    ModuleDescriptorCacheEntry(boolean isChanging, boolean isMissing, TimeProvider timeProvider) {
+    ModuleDescriptorCacheEntry(boolean isChanging, boolean isMissing, long createTimestamp, BigInteger moduleDescriptorHash, ModuleSource moduleSource) {
         this.isChanging = isChanging;
         this.isMissing = isMissing;
-        this.createTimestamp = timeProvider.getCurrentTime();
+        this.createTimestamp = createTimestamp;
+        this.moduleSource = moduleSource;
+        this.moduleDescriptorHash = moduleDescriptorHash;
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorStore.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorStore.java
index 334a4d5..03bf319 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorStore.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorStore.java
@@ -15,42 +15,37 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.modulecache;
 
-import org.apache.ivy.core.IvyPatternHelper;
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DefaultArtifact;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.apache.ivy.plugins.parser.ParserSettings;
-import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
 import org.gradle.api.Action;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.internal.artifacts.ivyservice.IvyModuleDescriptorWriter;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyContextualiser;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.IvyXmlModuleDescriptorParser;
 import org.gradle.api.internal.filestore.FileStoreEntry;
 import org.gradle.api.internal.filestore.PathKeyFileStore;
 import org.gradle.internal.UncheckedException;
 
 import java.io.File;
 import java.net.URL;
-import java.util.Collections;
 
 public class ModuleDescriptorStore {
 
-    private static final String DESCRIPTOR_ARTIFACT_PATTERN =
-            "module-metadata/[organisation]/[module](/[branch])/[revision]/[resolverId].ivy.xml";
-
-    private final XmlModuleDescriptorParser parser;
+    public static final String FILE_PATH_PATTERN = "module-metadata/%s/%s/%s/%s/ivy.xml";
+    private final IvyXmlModuleDescriptorParser parser;
     private final PathKeyFileStore pathKeyFileStore;
     private final IvyModuleDescriptorWriter ivyModuleDescriptorWriter;
 
-    public ModuleDescriptorStore(PathKeyFileStore pathKeyFileStore, IvyModuleDescriptorWriter ivyModuleDescriptorWriter, XmlModuleDescriptorParser xmlModuleDescriptorParser) {
+    public ModuleDescriptorStore(PathKeyFileStore pathKeyFileStore, IvyModuleDescriptorWriter ivyModuleDescriptorWriter, IvyXmlModuleDescriptorParser ivyXmlModuleDescriptorParser) {
         this.pathKeyFileStore = pathKeyFileStore;
         this.ivyModuleDescriptorWriter = ivyModuleDescriptorWriter;
-        parser = xmlModuleDescriptorParser;
+        parser = ivyXmlModuleDescriptorParser;
     }
 
-    public ModuleDescriptor getModuleDescriptor(ModuleVersionRepository repository, ModuleRevisionId moduleRevisionId) {
-        String filePath = getFilePath(repository, moduleRevisionId);
+    public ModuleDescriptor getModuleDescriptor(ModuleVersionRepository repository, ModuleVersionIdentifier moduleVersionIdentifier) {
+        String filePath = getFilePath(repository, moduleVersionIdentifier);
         final FileStoreEntry fileStoreEntry = pathKeyFileStore.get(filePath);
         if (fileStoreEntry != null) {
             return parseModuleDescriptorFile(fileStoreEntry.getFile());
@@ -58,10 +53,9 @@ public class ModuleDescriptorStore {
         return null;
     }
 
-
-    public void putModuleDescriptor(ModuleVersionRepository repository, final ModuleDescriptor moduleDescriptor) {
+    public FileStoreEntry putModuleDescriptor(ModuleVersionRepository repository, final ModuleDescriptor moduleDescriptor) {
         String filePath = getFilePath(repository, moduleDescriptor.getModuleRevisionId());
-        pathKeyFileStore.add(filePath, new Action<File>() {
+        return pathKeyFileStore.add(filePath, new Action<File>() {
             public void execute(File moduleDescriptorFile) {
                 try {
                     ivyModuleDescriptorWriter.write(moduleDescriptor, moduleDescriptorFile);
@@ -83,8 +77,10 @@ public class ModuleDescriptorStore {
     }
 
     private String getFilePath(ModuleVersionRepository repository, ModuleRevisionId moduleRevisionId) {
-        String resolverId = repository.getId();
-        Artifact artifact = new DefaultArtifact(moduleRevisionId, null, "ivy", "ivy", "xml", Collections.singletonMap("resolverId", resolverId));
-        return IvyPatternHelper.substitute(DESCRIPTOR_ARTIFACT_PATTERN, artifact);
+        return String.format(FILE_PATH_PATTERN, moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision(), repository.getId());
+    }
+
+    private String getFilePath(ModuleVersionRepository repository, ModuleVersionIdentifier moduleVersionIdentifier) {
+        return String.format(FILE_PATH_PATTERN, moduleVersionIdentifier.getGroup(), moduleVersionIdentifier.getName(), moduleVersionIdentifier.getVersion(), repository.getId());
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java
index 3caa7ee..c2b9f4b 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java
@@ -1,50 +1,50 @@
-/*
- * Copyright 2007 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class PublishModuleDescriptorConverter implements ModuleDescriptorConverter {
-    static final String IVY_MAVEN_NAMESPACE = "http://ant.apache.org/ivy/maven";
-    static final String IVY_MAVEN_NAMESPACE_PREFIX = "m";
-
-    private ModuleDescriptorConverter resolveModuleDescriptorConverter;
-    private ArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter;
-
-    public PublishModuleDescriptorConverter(ModuleDescriptorConverter resolveModuleDescriptorConverter,
-                                            ArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter) {
-        this.resolveModuleDescriptorConverter = resolveModuleDescriptorConverter;
-        this.artifactsToModuleDescriptorConverter = artifactsToModuleDescriptorConverter;
-    }
-
-    public ModuleDescriptor convert(Set<? extends Configuration> configurations, Module module) {
-        DefaultModuleDescriptor moduleDescriptor = (DefaultModuleDescriptor) resolveModuleDescriptorConverter
-                .convert(configurations, module);
-        moduleDescriptor.addExtraAttributeNamespace(IVY_MAVEN_NAMESPACE_PREFIX, IVY_MAVEN_NAMESPACE);
-        artifactsToModuleDescriptorConverter.addArtifacts(moduleDescriptor, configurations);
-        return moduleDescriptor;
-    }
-}
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class PublishModuleDescriptorConverter implements ModuleDescriptorConverter {
+    static final String IVY_MAVEN_NAMESPACE = "http://ant.apache.org/ivy/maven";
+    static final String IVY_MAVEN_NAMESPACE_PREFIX = "m";
+
+    private ModuleDescriptorConverter resolveModuleDescriptorConverter;
+    private ArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter;
+
+    public PublishModuleDescriptorConverter(ModuleDescriptorConverter resolveModuleDescriptorConverter,
+                                            ArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter) {
+        this.resolveModuleDescriptorConverter = resolveModuleDescriptorConverter;
+        this.artifactsToModuleDescriptorConverter = artifactsToModuleDescriptorConverter;
+    }
+
+    public ModuleDescriptor convert(Set<? extends Configuration> configurations, Module module) {
+         DefaultModuleDescriptor moduleDescriptor = (DefaultModuleDescriptor) resolveModuleDescriptorConverter
+                .convert(configurations, module);
+        moduleDescriptor.addExtraAttributeNamespace(IVY_MAVEN_NAMESPACE_PREFIX, IVY_MAVEN_NAMESPACE);
+        artifactsToModuleDescriptorConverter.addArtifacts(moduleDescriptor, configurations);
+        return moduleDescriptor;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java
index 5edc41a..03f6c9e 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java
@@ -18,6 +18,8 @@ package org.gradle.api.internal.artifacts.ivyservice.projectmodule;
 import org.apache.ivy.core.module.descriptor.Artifact;
 import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
 import org.gradle.api.internal.artifacts.ivyservice.*;
 
 import java.io.File;
@@ -36,7 +38,9 @@ public class ProjectDependencyResolver implements DependencyToModuleResolver {
     public void resolve(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionResolveResult result) {
         ModuleDescriptor moduleDescriptor = projectModuleRegistry.findProject(dependencyDescriptor);
         if (moduleDescriptor != null) {
-            result.resolved(moduleDescriptor.getModuleRevisionId(), moduleDescriptor, artifactResolver);
+            final ModuleRevisionId moduleRevisionId = moduleDescriptor.getModuleRevisionId();
+            final DefaultModuleVersionIdentifier moduleVersionIdentifier = new DefaultModuleVersionIdentifier(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
+            result.resolved(moduleVersionIdentifier, moduleDescriptor, artifactResolver);
         } else {
             resolver.resolve(dependencyDescriptor, result);
         }
@@ -45,7 +49,7 @@ public class ProjectDependencyResolver implements DependencyToModuleResolver {
     private static class ProjectArtifactResolver implements ArtifactResolver {
         public void resolve(Artifact artifact, BuildableArtifactResolveResult result) {
             String path = artifact.getExtraAttribute(DefaultIvyDependencyPublisher.FILE_ABSOLUTE_PATH_EXTRA_ATTRIBUTE);
-            result.resolved(new File(path), null);
+            result.resolved(new File(path));
         }
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicy.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicy.java
new file mode 100644
index 0000000..6d20567
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicy.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.ArtifactIdentifier;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.ResolvedModuleVersion;
+import org.gradle.api.artifacts.cache.*;
+import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public class DefaultCachePolicy implements CachePolicy, ResolutionRules {
+    private static final int SECONDS_IN_DAY = 24 * 60 * 60;
+
+    final List<Action<? super DependencyResolutionControl>> dependencyCacheRules;
+    final List<Action<? super ModuleResolutionControl>> moduleCacheRules;
+    final List<Action<? super ArtifactResolutionControl>> artifactCacheRules;
+
+    public DefaultCachePolicy() {
+        this.dependencyCacheRules = new ArrayList<Action<? super DependencyResolutionControl>>();
+        this.moduleCacheRules = new ArrayList<Action<? super ModuleResolutionControl>>();
+        this.artifactCacheRules = new ArrayList<Action<? super ArtifactResolutionControl>>();
+
+        cacheDynamicVersionsFor(SECONDS_IN_DAY, TimeUnit.SECONDS);
+        cacheChangingModulesFor(SECONDS_IN_DAY, TimeUnit.SECONDS);
+        cacheMissingModulesAndArtifactsFor(SECONDS_IN_DAY, TimeUnit.SECONDS);
+    }
+
+    DefaultCachePolicy(DefaultCachePolicy policy) {
+        this.dependencyCacheRules = new ArrayList<Action<? super DependencyResolutionControl>>(policy.dependencyCacheRules);
+        this.moduleCacheRules = new ArrayList<Action<? super ModuleResolutionControl>>(policy.moduleCacheRules);
+        this.artifactCacheRules = new ArrayList<Action<? super ArtifactResolutionControl>>(policy.artifactCacheRules);
+    }
+
+    public void eachDependency(Action<? super DependencyResolutionControl> rule) {
+        dependencyCacheRules.add(0, rule);
+    }
+
+    public void eachModule(Action<? super ModuleResolutionControl> rule) {
+        moduleCacheRules.add(0, rule);
+    }
+
+    public void eachArtifact(Action<? super ArtifactResolutionControl> rule) {
+        artifactCacheRules.add(0, rule);
+    }
+
+    public void cacheDynamicVersionsFor(final int value, final TimeUnit unit) {
+        eachDependency(new Action<DependencyResolutionControl>() {
+            public void execute(DependencyResolutionControl dependencyResolutionControl) {
+                dependencyResolutionControl.cacheFor(value, unit);
+            }
+        });
+    }
+
+    public void cacheChangingModulesFor(final int value, final TimeUnit units) {
+        eachModule(new Action<ModuleResolutionControl>() {
+            public void execute(ModuleResolutionControl moduleResolutionControl) {
+                if (moduleResolutionControl.isChanging()) {
+                    moduleResolutionControl.cacheFor(value, units);
+                }
+            }
+        });
+        eachArtifact(new Action<ArtifactResolutionControl>() {
+            public void execute(ArtifactResolutionControl artifactResolutionControl) {
+                if (artifactResolutionControl.belongsToChangingModule()) {
+                    artifactResolutionControl.cacheFor(value, units);
+                }
+            }
+        });
+    }
+
+    private void cacheMissingModulesAndArtifactsFor(final int value, final TimeUnit units) {
+        eachModule(new Action<ModuleResolutionControl>() {
+            public void execute(ModuleResolutionControl moduleResolutionControl) {
+                if (moduleResolutionControl.getCachedResult() == null) {
+                    moduleResolutionControl.cacheFor(value, units);
+                }
+            }
+        });
+        eachArtifact(new Action<ArtifactResolutionControl>() {
+            public void execute(ArtifactResolutionControl artifactResolutionControl) {
+                if (artifactResolutionControl.getCachedResult() == null) {
+                    artifactResolutionControl.cacheFor(value, units);
+                }
+            }
+        });
+    }
+
+    public boolean mustRefreshDynamicVersion(ModuleVersionSelector selector, ModuleVersionIdentifier moduleId, long ageMillis) {
+        CachedDependencyResolutionControl dependencyResolutionControl = new CachedDependencyResolutionControl(selector, moduleId, ageMillis);
+
+        for (Action<? super DependencyResolutionControl> rule : dependencyCacheRules) {
+            rule.execute(dependencyResolutionControl);
+            if (dependencyResolutionControl.ruleMatch()) {
+                return dependencyResolutionControl.mustCheck();
+            }
+        }
+
+        return false;
+    }
+
+    public boolean mustRefreshModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion resolvedModuleVersion, ModuleRevisionId moduleRevisionId, final long ageMillis) {
+        return mustRefreshModule(moduleVersionId, resolvedModuleVersion, ageMillis, false);
+    }
+
+    public boolean mustRefreshChangingModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion resolvedModuleVersion, long ageMillis) {
+        return mustRefreshModule(moduleVersionId, resolvedModuleVersion, ageMillis, true);
+    }
+
+    private boolean mustRefreshModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion version, long ageMillis, boolean changingModule) {
+        CachedModuleResolutionControl moduleResolutionControl = new CachedModuleResolutionControl(moduleVersionId, version, changingModule, ageMillis);
+
+        for (Action<? super ModuleResolutionControl> rule : moduleCacheRules) {
+            rule.execute(moduleResolutionControl);
+            if (moduleResolutionControl.ruleMatch()) {
+                return moduleResolutionControl.mustCheck();
+            }
+        }
+
+        return false;
+    }
+
+    public boolean mustRefreshArtifact(ArtifactIdentifier artifactIdentifier, File cachedArtifactFile, long ageMillis, boolean belongsToChangingModule, boolean moduleDescriptorInSync) {
+        CachedArtifactResolutionControl artifactResolutionControl = new CachedArtifactResolutionControl(artifactIdentifier, cachedArtifactFile, ageMillis, belongsToChangingModule);
+        if(belongsToChangingModule && !moduleDescriptorInSync){
+            return true;
+        }
+        for (Action<? super ArtifactResolutionControl> rule : artifactCacheRules) {
+            rule.execute(artifactResolutionControl);
+            if (artifactResolutionControl.ruleMatch()) {
+                return artifactResolutionControl.mustCheck();
+            }
+        }
+        return false;
+    }
+
+    DefaultCachePolicy copy() {
+        return new DefaultCachePolicy(this);
+    }
+
+    private abstract static class AbstractResolutionControl<A, B> implements ResolutionControl<A, B> {
+        private final A request;
+        private final B cachedResult;
+        private final long ageMillis;
+        private boolean ruleMatch;
+        private boolean mustCheck;
+
+        private AbstractResolutionControl(A request, B cachedResult, long ageMillis) {
+            this.request = request;
+            this.cachedResult = cachedResult;
+            this.ageMillis = ageMillis;
+        }
+
+        public A getRequest() {
+            return request;
+        }
+
+        public B getCachedResult() {
+            return cachedResult;
+        }
+
+        public void cacheFor(int value, TimeUnit units) {
+            long timeoutMillis = TimeUnit.MILLISECONDS.convert(value, units);
+            if (ageMillis <= timeoutMillis) {
+                setMustCheck(false);
+            } else {
+                setMustCheck(true);
+            }
+        }
+
+        public void useCachedResult() {
+            setMustCheck(false);
+        }
+
+        public void refresh() {
+            setMustCheck(true);
+        }
+
+        private void setMustCheck(boolean val) {
+            ruleMatch = true;
+            mustCheck = val;
+        }
+
+        public boolean ruleMatch() {
+            return ruleMatch;
+        }
+
+        public boolean mustCheck() {
+            return mustCheck;
+        }
+    }
+
+    private class CachedDependencyResolutionControl extends AbstractResolutionControl<ModuleVersionSelector, ModuleVersionIdentifier> implements DependencyResolutionControl {
+        private CachedDependencyResolutionControl(ModuleVersionSelector request, ModuleVersionIdentifier cachedVersion, long ageMillis) {
+            super(request, cachedVersion, ageMillis);
+        }
+    }
+
+    private class CachedModuleResolutionControl extends AbstractResolutionControl<ModuleVersionIdentifier, ResolvedModuleVersion> implements ModuleResolutionControl {
+        private final boolean changing;
+
+        private CachedModuleResolutionControl(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion cachedVersion, boolean changing, long ageMillis) {
+            super(moduleVersionId, cachedVersion, ageMillis);
+            this.changing = changing;
+        }
+
+        public boolean isChanging() {
+            return changing;
+        }
+    }
+
+    private class CachedArtifactResolutionControl extends AbstractResolutionControl<ArtifactIdentifier, File> implements ArtifactResolutionControl {
+        private final boolean belongsToChangingModule;
+
+        private CachedArtifactResolutionControl(ArtifactIdentifier artifactIdentifier, File cachedResult, long ageMillis, boolean belongsToChangingModule) {
+            super(artifactIdentifier, cachedResult, ageMillis);
+            this.belongsToChangingModule = belongsToChangingModule;
+        }
+
+        public boolean belongsToChangingModule() {
+            return belongsToChangingModule;
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategy.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategy.java
new file mode 100644
index 0000000..3be653c
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategy.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy;
+
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.ConflictResolution;
+import org.gradle.api.artifacts.DependencyResolveDetails;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.ResolutionStrategy;
+import org.gradle.api.artifacts.cache.ResolutionRules;
+import org.gradle.api.internal.Actions;
+import org.gradle.api.internal.artifacts.DependencyResolveDetailsInternal;
+import org.gradle.api.internal.artifacts.configurations.ResolutionStrategyInternal;
+import org.gradle.api.internal.artifacts.dsl.ForcedModuleNotationParser;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import static org.gradle.util.GUtil.flattenElements;
+
+/**
+ * by Szczepan Faber, created at: 10/7/11
+ */
+public class DefaultResolutionStrategy implements ResolutionStrategyInternal {
+
+    private Set<ModuleVersionSelector> forcedModules = new LinkedHashSet<ModuleVersionSelector>();
+    private ConflictResolution conflictResolution = new LatestConflictResolution();
+
+    final Set<Action<? super DependencyResolveDetails>> dependencyResolveRules;
+    private final DefaultCachePolicy cachePolicy;
+
+    public DefaultResolutionStrategy() {
+        this(new DefaultCachePolicy(), new LinkedHashSet<Action<? super DependencyResolveDetails>>());
+    }
+
+    DefaultResolutionStrategy(DefaultCachePolicy cachePolicy, Set<Action<? super DependencyResolveDetails>> dependencyResolveRules) {
+        this.cachePolicy = cachePolicy;
+        this.dependencyResolveRules = dependencyResolveRules;
+    }
+
+    public Set<ModuleVersionSelector> getForcedModules() {
+        return forcedModules;
+    }
+
+    public ResolutionStrategy failOnVersionConflict() {
+        this.conflictResolution = new StrictConflictResolution();
+        return this;
+    }
+
+    public ConflictResolution getConflictResolution() {
+        return this.conflictResolution;
+    }
+
+    public ResolutionRules getResolutionRules() {
+        return cachePolicy;
+    }
+
+    public DefaultResolutionStrategy force(Object... forcedModuleNotations) {
+        assert forcedModuleNotations != null : "forcedModuleNotations cannot be null";
+        Set<ModuleVersionSelector> modules = new ForcedModuleNotationParser().parseNotation(forcedModuleNotations);
+        this.forcedModules.addAll(modules);
+        return this;
+    }
+
+    public ResolutionStrategy eachDependency(Action<? super DependencyResolveDetails> rule) {
+        dependencyResolveRules.add(rule);
+        return this;
+    }
+
+    public Action<DependencyResolveDetailsInternal> getDependencyResolveRule() {
+        Collection allRules = flattenElements(new ModuleForcingResolveRule(forcedModules), dependencyResolveRules);
+        return Actions.composite(allRules);
+    }
+
+    public DefaultResolutionStrategy setForcedModules(Object ... forcedModuleNotations) {
+        Set<ModuleVersionSelector> forcedModules = new ForcedModuleNotationParser().parseNotation(forcedModuleNotations);
+        this.forcedModules = forcedModules;
+        return this;
+    }
+
+    public DefaultCachePolicy getCachePolicy() {
+        return cachePolicy;
+    }
+
+    public void cacheDynamicVersionsFor(int value, String units) {
+        TimeUnit timeUnit = TimeUnit.valueOf(units.toUpperCase());
+        cacheDynamicVersionsFor(value, timeUnit);
+    }
+
+    public void cacheDynamicVersionsFor(int value, TimeUnit units) {
+        this.cachePolicy.cacheDynamicVersionsFor(value, units);
+    }
+
+    public void cacheChangingModulesFor(int value, String units) {
+        TimeUnit timeUnit = TimeUnit.valueOf(units.toUpperCase());
+        cacheChangingModulesFor(value, timeUnit);
+    }
+
+    public void cacheChangingModulesFor(int value, TimeUnit units) {
+        this.cachePolicy.cacheChangingModulesFor(value, units);
+    }
+
+    public DefaultResolutionStrategy copy() {
+        DefaultResolutionStrategy out = new DefaultResolutionStrategy(cachePolicy.copy(),
+                new LinkedHashSet<Action<? super DependencyResolveDetails>>(dependencyResolveRules));
+
+        if (conflictResolution instanceof StrictConflictResolution) {
+            out.failOnVersionConflict();
+        }
+        out.setForcedModules(getForcedModules());
+        return out;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/LatestConflictResolution.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/LatestConflictResolution.java
new file mode 100644
index 0000000..a8022db
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/LatestConflictResolution.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy;
+
+import org.gradle.api.artifacts.ConflictResolution;
+
+/**
+ * Latest resolution strategy
+ * <p>
+ * by Szczepan Faber, created at: 10/5/11
+ */
+public class LatestConflictResolution implements ConflictResolution {
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRule.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRule.java
new file mode 100644
index 0000000..4992512
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRule.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy;
+
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.ModuleIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.internal.artifacts.DefaultModuleIdentifier;
+import org.gradle.api.internal.artifacts.DependencyResolveDetailsInternal;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+* by Szczepan Faber, created at: 11/29/12
+*/
+public class ModuleForcingResolveRule implements Action<DependencyResolveDetailsInternal> {
+
+    private final Map<ModuleIdentifier, String> forcedModules;
+
+    public ModuleForcingResolveRule(Collection<? extends ModuleVersionSelector> forcedModules) {
+        if (!forcedModules.isEmpty()) {
+            this.forcedModules = new HashMap<ModuleIdentifier, String>();
+            for (ModuleVersionSelector module : forcedModules) {
+                this.forcedModules.put(new DefaultModuleIdentifier(module.getGroup(), module.getName()), module.getVersion());
+            }
+        } else {
+            this.forcedModules = null;
+        }
+    }
+
+    public void execute(DependencyResolveDetailsInternal details) {
+        if (forcedModules == null) {
+            return;
+        }
+        ModuleIdentifier key = new DefaultModuleIdentifier(details.getRequested().getGroup(), details.getRequested().getName());
+        if (forcedModules.containsKey(key)) {
+            details.useVersion(forcedModules.get(key), VersionSelectionReasons.FORCED);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/StrictConflictResolution.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/StrictConflictResolution.java
new file mode 100644
index 0000000..4ee1533
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/StrictConflictResolution.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy;
+
+import org.gradle.api.artifacts.ConflictResolution;
+
+/**
+ * Strict type, allows configuring (forcing) certain dependency versions using dependency notation
+ * <p>
+ * by Szczepan Faber, created at: 10/5/11
+ */
+public class StrictConflictResolution implements ConflictResolution {
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyResolver.java
index a69f82f..73fad93 100755
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyResolver.java
@@ -19,7 +19,6 @@ import org.gradle.api.artifacts.ResolveException;
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
 import org.gradle.api.internal.artifacts.ResolverResults;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
-import org.gradle.api.internal.artifacts.configurations.conflicts.StrictConflictResolution;
 import org.gradle.api.internal.artifacts.ivyservice.*;
 import org.gradle.api.internal.artifacts.ivyservice.clientmodule.ClientModuleResolver;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAdapter;
@@ -27,6 +26,7 @@ import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.LazyDependencyToM
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ResolveIvyFactory;
 import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectDependencyResolver;
 import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectModuleRegistry;
+import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.StrictConflictResolution;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ResolutionResultBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -55,7 +55,7 @@ public class DefaultDependencyResolver implements ArtifactDependencyResolver {
         dependencyResolver = new ClientModuleResolver(dependencyResolver);
         dependencyResolver = new ProjectDependencyResolver(projectModuleRegistry, dependencyResolver);
         DependencyToModuleVersionIdResolver idResolver = new LazyDependencyToModuleResolver(dependencyResolver, ivyAdapter.getResolveData().getSettings().getVersionMatcher());
-        idResolver = new VersionForcingDependencyToModuleResolver(idResolver, configuration.getResolutionStrategy().getForcedModules());
+        idResolver = new VersionForcingDependencyToModuleResolver(idResolver, configuration.getResolutionStrategy().getDependencyResolveRule());
 
         ModuleConflictResolver conflictResolver;
         if (configuration.getResolutionStrategy().getConflictResolution() instanceof StrictConflictResolution) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilder.java
index 777ff59..e007e03 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilder.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilder.java
@@ -48,13 +48,13 @@ public class DependencyGraphBuilder {
     private final ModuleDescriptorConverter moduleDescriptorConverter;
     private final ResolvedArtifactFactory resolvedArtifactFactory;
     private final DependencyToModuleVersionIdResolver dependencyResolver;
-    private final ForcedModuleConflictResolver conflictResolver;
+    private final InternalConflictResolver conflictResolver;
 
     public DependencyGraphBuilder(ModuleDescriptorConverter moduleDescriptorConverter, ResolvedArtifactFactory resolvedArtifactFactory, DependencyToModuleVersionIdResolver dependencyResolver, ModuleConflictResolver conflictResolver) {
         this.moduleDescriptorConverter = moduleDescriptorConverter;
         this.resolvedArtifactFactory = resolvedArtifactFactory;
         this.dependencyResolver = dependencyResolver;
-        this.conflictResolver = new ForcedModuleConflictResolver(conflictResolver);
+        this.conflictResolver = new InternalConflictResolver(conflictResolver);
     }
 
     public DefaultLenientConfiguration resolve(ConfigurationInternal configuration, ResolveData resolveData, ResolvedConfigurationListener listener) throws ResolveException {
@@ -412,6 +412,10 @@ public class DependencyGraphBuilder {
             return selector.module.selected;
         }
 
+        public ModuleVersionSelectionReason getReason() {
+            return selector.module.selected == null ? selector.idSelectionReason : selector.module.selected.selectionReason;
+        }
+
         public void collectFailures(FailureState failureState) {
             if (isFailed()) {
                 failureState.addUnresolvedDependency(this, selector.descriptor.getDependencyRevisionId(), getFailure());
@@ -433,7 +437,8 @@ public class DependencyGraphBuilder {
         public ResolveState(ModuleDescriptor rootModule, String rootConfigurationName, DependencyToModuleVersionIdResolver resolver, ResolveData resolveData) {
             this.resolver = resolver;
             this.resolveData = resolveData;
-            DefaultModuleRevisionResolveState rootVersion = getRevision(rootModule.getModuleRevisionId());
+            final ModuleRevisionId moduleRevisionId = rootModule.getModuleRevisionId();
+            DefaultModuleRevisionResolveState rootVersion = getRevision(new DefaultModuleVersionIdentifier(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision()));
             rootVersion.setDescriptor(rootModule);
             root = getConfigurationNode(rootVersion, rootConfigurationName);
             root.moduleRevision.module.select(root.moduleRevision);
@@ -449,8 +454,8 @@ public class DependencyGraphBuilder {
             return module;
         }
 
-        public DefaultModuleRevisionResolveState getRevision(ModuleRevisionId moduleRevisionId) {
-            ModuleRevisionId id = new ModuleRevisionId(new ModuleId(moduleRevisionId.getOrganisation(), moduleRevisionId.getName()), moduleRevisionId.getRevision());
+        public DefaultModuleRevisionResolveState getRevision(ModuleVersionIdentifier moduleVersionIdentifier) {
+            ModuleRevisionId id = new ModuleRevisionId(new ModuleId(moduleVersionIdentifier.getGroup(), moduleVersionIdentifier.getName()), moduleVersionIdentifier.getVersion());
             return getModule(id.getModuleId()).getVersion(id);
         }
 
@@ -876,6 +881,7 @@ public class DependencyGraphBuilder {
         final ResolveState resolveState;
         final ModuleResolveState module;
         ModuleVersionResolveException failure;
+        ModuleVersionSelectionReason idSelectionReason;
         DefaultModuleRevisionResolveState targetModuleRevision;
         ModuleVersionIdResolveResult idResolveResult;
         ModuleVersionResolveResult resolveResult;
@@ -908,6 +914,7 @@ public class DependencyGraphBuilder {
             }
 
             idResolveResult = resolver.resolve(descriptor);
+            idSelectionReason = idResolveResult.getSelectionReason();
             if (idResolveResult.getFailure() != null) {
                 failure = idResolveResult.getFailure();
                 return null;
@@ -915,10 +922,7 @@ public class DependencyGraphBuilder {
 
             targetModuleRevision = resolveState.getRevision(idResolveResult.getId());
             targetModuleRevision.addResolver(this);
-
-            if (idResolveResult.getSelectionReason() == ModuleVersionIdResolveResult.IdSelectionReason.forced) {
-                targetModuleRevision.selectionReason = VersionSelectionReasons.FORCED;
-            }
+            targetModuleRevision.selectionReason = idResolveResult.getSelectionReason();
 
             return targetModuleRevision;
         }
@@ -945,10 +949,10 @@ public class DependencyGraphBuilder {
         }
     }
 
-    private static class ForcedModuleConflictResolver {
+    private static class InternalConflictResolver {
         private final ModuleConflictResolver resolver;
 
-        private ForcedModuleConflictResolver(ModuleConflictResolver resolver) {
+        private InternalConflictResolver(ModuleConflictResolver resolver) {
             this.resolver = resolver;
         }
 
@@ -963,7 +967,11 @@ public class DependencyGraphBuilder {
             }
             //TODO SF unit test
             DefaultModuleRevisionResolveState out = (DefaultModuleRevisionResolveState) resolver.select(candidates, root);
-            out.selectionReason = VersionSelectionReasons.CONFLICT_RESOLUTION;
+            if (out.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE) {
+                out.selectionReason = VersionSelectionReasons.CONFLICT_RESOLUTION_BY_RULE;
+            } else {
+                out.selectionReason = VersionSelectionReasons.CONFLICT_RESOLUTION;
+            }
             return out;
         }
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/CachingDependencyResultFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/CachingDependencyResultFactory.java
index d226e57..95d6eff 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/CachingDependencyResultFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/CachingDependencyResultFactory.java
@@ -17,12 +17,15 @@
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine.result;
 
 import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.artifacts.result.DependencyResult;
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.result.ResolvedDependencyResult;
 import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
+import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
 import org.gradle.api.internal.artifacts.result.DefaultResolvedDependencyResult;
 import org.gradle.api.internal.artifacts.result.DefaultUnresolvedDependencyResult;
 
-import java.util.LinkedHashMap;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -33,18 +36,19 @@ import static java.util.Arrays.asList;
  */
 public class CachingDependencyResultFactory {
 
-    Map<List, DefaultUnresolvedDependencyResult> unresolvedDependencies = new LinkedHashMap();
-    Map<List, DefaultResolvedDependencyResult> resolvedDependencies = new LinkedHashMap();
+    private final Map<List, DefaultUnresolvedDependencyResult> unresolvedDependencies = new HashMap<List, DefaultUnresolvedDependencyResult>();
+    private final Map<List, DefaultResolvedDependencyResult> resolvedDependencies = new HashMap<List, DefaultResolvedDependencyResult>();
 
-    public DependencyResult createUnresolvedDependency(ModuleVersionSelector requested, ResolvedModuleVersionResult from, Exception failure) {
+    public UnresolvedDependencyResult createUnresolvedDependency(ModuleVersionSelector requested, ResolvedModuleVersionResult from,
+                                                       ModuleVersionSelectionReason reason, ModuleVersionResolveException failure) {
         List<Object> key = asList(requested, from);
         if (!unresolvedDependencies.containsKey(key)) {
-            unresolvedDependencies.put(key, new DefaultUnresolvedDependencyResult(requested, failure, from));
+            unresolvedDependencies.put(key, new DefaultUnresolvedDependencyResult(requested, reason, from, failure));
         }
         return unresolvedDependencies.get(key);
     }
 
-    public DependencyResult createResolvedDependency(ModuleVersionSelector requested, ResolvedModuleVersionResult from, ResolvedModuleVersionResult selected) {
+    public ResolvedDependencyResult createResolvedDependency(ModuleVersionSelector requested, ResolvedModuleVersionResult from, ResolvedModuleVersionResult selected) {
         List<Object> key = asList(requested, from, selected);
         if (!resolvedDependencies.containsKey(key)) {
             resolvedDependencies.put(key, new DefaultResolvedDependencyResult(requested, selected, from));
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/InternalDependencyResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/InternalDependencyResult.java
index 4ad0280..13250d2 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/InternalDependencyResult.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/InternalDependencyResult.java
@@ -18,6 +18,8 @@ package org.gradle.api.internal.artifacts.ivyservice.resolveengine.result;
 
 import org.gradle.api.Nullable;
 import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
 
 /**
  * by Szczepan Faber, created at: 8/24/12
@@ -26,7 +28,11 @@ public interface InternalDependencyResult {
 
     ModuleVersionSelector getRequested();
 
-    @Nullable Exception getFailure();
+    @Nullable
+    ModuleVersionResolveException getFailure();
 
-    @Nullable ModuleVersionSelection getSelected();
+    @Nullable
+    ModuleVersionSelection getSelected();
+
+    ModuleVersionSelectionReason getReason();
 }
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolutionResultBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolutionResultBuilder.java
index eadfc18..2323616 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolutionResultBuilder.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolutionResultBuilder.java
@@ -55,14 +55,15 @@ public class ResolutionResultBuilder implements ResolvedConfigurationListener {
     public void resolvedConfiguration(ModuleVersionIdentifier id, Collection<? extends InternalDependencyResult> dependencies) {
         for (InternalDependencyResult d : dependencies) {
             DefaultResolvedModuleVersionResult from = modules.get(id);
+            DependencyResult dependency;
             if (d.getFailure() != null) {
-                from.addDependency(dependencyResultFactory.createUnresolvedDependency(d.getRequested(), from, d.getFailure()));
+                dependency = dependencyResultFactory.createUnresolvedDependency(d.getRequested(), from, d.getReason(), d.getFailure());
             } else {
                 DefaultResolvedModuleVersionResult selected = modules.get(d.getSelected().getSelectedId());
-                DependencyResult dependency = dependencyResultFactory.createResolvedDependency(d.getRequested(), from, selected);
-                from.addDependency(dependency);
+                dependency = dependencyResultFactory.createResolvedDependency(d.getRequested(), from, selected);
                 selected.addDependent((ResolvedDependencyResult) dependency);
             }
+            from.addDependency(dependency);
         }
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/VersionSelectionReasons.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/VersionSelectionReasons.java
index 897d35b..9a221f4 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/VersionSelectionReasons.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/VersionSelectionReasons.java
@@ -22,19 +22,26 @@ import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
  * by Szczepan Faber, created at: 10/1/12
  */
 public class VersionSelectionReasons {
-    public static final ModuleVersionSelectionReason REQUESTED = new DefaultModuleVersionSelectionReason(false, false);
-    public static final ModuleVersionSelectionReason ROOT = new DefaultModuleVersionSelectionReason(false, false);
-    public static final ModuleVersionSelectionReason FORCED = new DefaultModuleVersionSelectionReason(true, false);
-    public static final ModuleVersionSelectionReason CONFLICT_RESOLUTION = new DefaultModuleVersionSelectionReason(false, true);
+    public static final ModuleVersionSelectionReason REQUESTED = new DefaultModuleVersionSelectionReason(false, false, false, "requested");
+    public static final ModuleVersionSelectionReason ROOT = new DefaultModuleVersionSelectionReason(false, false, false, "root");
+    public static final ModuleVersionSelectionReason FORCED = new DefaultModuleVersionSelectionReason(true, false, false, "forced");
+    public static final ModuleVersionSelectionReason CONFLICT_RESOLUTION = new DefaultModuleVersionSelectionReason(false, true, false, "conflict resolution");
+    public static final ModuleVersionSelectionReason SELECTED_BY_RULE = new DefaultModuleVersionSelectionReason(false, false, true, "selected by rule");
+    public static final ModuleVersionSelectionReason CONFLICT_RESOLUTION_BY_RULE = new DefaultModuleVersionSelectionReason(false, true, true, "conflict resolution by rule");
 
     private static class DefaultModuleVersionSelectionReason implements ModuleVersionSelectionReason {
 
         private final boolean forced;
         private final boolean conflictResolution;
+        private final boolean selectedByRule;
+        private final String description;
 
-        private DefaultModuleVersionSelectionReason(boolean forced, boolean conflictResolution) {
+        private DefaultModuleVersionSelectionReason(boolean forced, boolean conflictResolution, boolean selectedByRule, String description) {
             this.forced = forced;
             this.conflictResolution = conflictResolution;
+            this.selectedByRule = selectedByRule;
+            assert description != null;
+            this.description = description;
         }
 
         public boolean isForced() {
@@ -45,7 +52,15 @@ public class VersionSelectionReasons {
             return conflictResolution;
         }
 
+        public boolean isSelectedByRule() {
+            return selectedByRule;
+        }
+
+        public String getDescription() {
+            return description;
+        }
+
         //TODO At some point we want to provide information if version was requested in the graph.
         //Perhaps a method like isRequested(). Not requested means that some particular version was forced but no dependency have requested this version.
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactory.java
index b04d8ab..84fdf55 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 the original author or authors.
+ * Copyright 2012 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@ import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.artifacts.repositories.*;
-import org.gradle.api.internal.artifacts.ArtifactPublisherFactory;
 import org.gradle.api.internal.artifacts.BaseRepositoryFactory;
 import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator;
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory;
@@ -47,7 +46,6 @@ public class DefaultBaseRepositoryFactory implements BaseRepositoryFactory {
     private final ProgressLoggerFactory progressLoggerFactory;
     private final RepositoryCacheManager localCacheManager;
     private final RepositoryCacheManager downloadingCacheManager;
-    private final ArtifactPublisherFactory artifactPublisherFactory;
 
     public DefaultBaseRepositoryFactory(LocalMavenRepositoryLocator localMavenRepositoryLocator,
                                         FileResolver fileResolver,
@@ -56,8 +54,7 @@ public class DefaultBaseRepositoryFactory implements BaseRepositoryFactory {
                                         LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder,
                                         ProgressLoggerFactory progressLoggerFactory,
                                         RepositoryCacheManager localCacheManager,
-                                        RepositoryCacheManager downloadingCacheManager,
-                                        ArtifactPublisherFactory artifactPublisherFactory) {
+                                        RepositoryCacheManager downloadingCacheManager) {
         this.localMavenRepositoryLocator = localMavenRepositoryLocator;
         this.fileResolver = fileResolver;
         this.instantiator = instantiator;
@@ -66,7 +63,6 @@ public class DefaultBaseRepositoryFactory implements BaseRepositoryFactory {
         this.progressLoggerFactory = progressLoggerFactory;
         this.localCacheManager = localCacheManager;
         this.downloadingCacheManager = downloadingCacheManager;
-        this.artifactPublisherFactory = artifactPublisherFactory;
     }
 
     public ArtifactRepository createRepository(Object userDescription) {
@@ -113,7 +109,7 @@ public class DefaultBaseRepositoryFactory implements BaseRepositoryFactory {
 
     public IvyArtifactRepository createIvyRepository() {
         return instantiator.newInstance(DefaultIvyArtifactRepository.class, fileResolver, createPasswordCredentials(), transportFactory,
-                locallyAvailableResourceFinder, artifactPublisherFactory
+                locallyAvailableResourceFinder, instantiator
         );
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepository.java
index 8bf097c..c356d64 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepository.java
@@ -19,15 +19,15 @@ import groovy.lang.Closure;
 import org.apache.ivy.core.module.id.ArtifactRevisionId;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
 import org.gradle.api.artifacts.repositories.PasswordCredentials;
-import org.gradle.api.internal.artifacts.ArtifactPublisherFactory;
 import org.gradle.api.internal.artifacts.repositories.layout.*;
 import org.gradle.api.internal.artifacts.repositories.resolver.IvyResolver;
 import org.gradle.api.internal.artifacts.repositories.resolver.PatternBasedResolver;
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory;
 import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.publish.ivy.internal.IvyPublisher;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.util.ConfigureUtil;
 import org.gradle.util.WrapUtil;
 
@@ -35,25 +35,24 @@ import java.net.URI;
 import java.util.LinkedHashSet;
 import java.util.Set;
 
-public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupportedRepository implements IvyArtifactRepositoryInternal {
+public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupportedRepository implements IvyArtifactRepository, ArtifactRepositoryInternal {
     private Object baseUrl;
     private RepositoryLayout layout;
     private final AdditionalPatternsRepositoryLayout additionalPatternsLayout;
     private final FileResolver fileResolver;
     private final RepositoryTransportFactory transportFactory;
     private final LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder;
-    private final ArtifactPublisherFactory artifactPublisherFactory;
+    private final Instantiator instantiator;
 
     public DefaultIvyArtifactRepository(FileResolver fileResolver, PasswordCredentials credentials, RepositoryTransportFactory transportFactory,
-                                        LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder,
-                                        ArtifactPublisherFactory artifactPublisherFactory) {
+                                        LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder, Instantiator instantiator) {
         super(credentials);
         this.fileResolver = fileResolver;
         this.transportFactory = transportFactory;
         this.locallyAvailableResourceFinder = locallyAvailableResourceFinder;
         this.additionalPatternsLayout = new AdditionalPatternsRepositoryLayout(fileResolver);
         this.layout = new GradleRepositoryLayout();
-        this.artifactPublisherFactory = artifactPublisherFactory;
+        this.instantiator = instantiator;
     }
 
     public DependencyResolver createResolver() {
@@ -105,11 +104,11 @@ public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupporte
 
     public void layout(String layoutName) {
         if ("maven".equals(layoutName)) {
-            layout = new MavenRepositoryLayout();
+            layout = instantiator.newInstance(MavenRepositoryLayout.class);
         } else if ("pattern".equals(layoutName)) {
-            layout = new PatternRepositoryLayout();
+            layout = instantiator.newInstance(PatternRepositoryLayout.class);
         } else {
-            layout = new GradleRepositoryLayout();
+            layout = instantiator.newInstance(GradleRepositoryLayout.class);
         }
     }
 
@@ -154,9 +153,4 @@ public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupporte
         }
     }
 
-    public IvyPublisher createPublisher() {
-        return new IvyPublisher(artifactPublisherFactory.createArtifactPublisher(this));
-    }
-
-
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/IvyArtifactRepositoryInternal.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/IvyArtifactRepositoryInternal.java
deleted file mode 100644
index d34b523..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/IvyArtifactRepositoryInternal.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.repositories;
-
-import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
-import org.gradle.api.publish.ivy.internal.IvyPublisher;
-
-public interface IvyArtifactRepositoryInternal extends IvyArtifactRepository, ArtifactRepositoryInternal {
-
-    IvyPublisher createPublisher();
-
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryCacheManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryCacheManager.java
index 40cdadc..94c9032 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryCacheManager.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryCacheManager.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.api.internal.artifacts.repositories.cachemanager;
 
+import org.apache.ivy.core.cache.ArtifactOrigin;
 import org.apache.ivy.core.cache.CacheDownloadOptions;
 import org.apache.ivy.core.cache.CacheMetadataOptions;
 import org.apache.ivy.core.cache.DownloadListener;
@@ -33,7 +34,6 @@ import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.apache.ivy.plugins.resolver.util.ResolvedResource;
 import org.apache.ivy.util.Message;
 import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactOriginWithMetaData;
 import org.gradle.api.internal.externalresource.ExternalResource;
 import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
@@ -65,7 +65,7 @@ public class DownloadingRepositoryCacheManager extends AbstractRepositoryCacheMa
     }
 
     public EnhancedArtifactDownloadReport download(Artifact artifact, ArtifactResourceResolver resourceResolver,
-                                           ResourceDownloader resourceDownloader, CacheDownloadOptions options) {
+                                                   ResourceDownloader resourceDownloader, CacheDownloadOptions options) {
         EnhancedArtifactDownloadReport adr = new EnhancedArtifactDownloadReport(artifact);
 
         DownloadListener listener = options.getListener();
@@ -77,7 +77,8 @@ public class DownloadingRepositoryCacheManager extends AbstractRepositoryCacheMa
         try {
             ResolvedResource artifactRef = resourceResolver.resolve(artifact);
             if (artifactRef != null) {
-                ArtifactOriginWithMetaData origin = new ArtifactOriginWithMetaData(artifact, artifactRef.getResource());
+                final Resource resource = artifactRef.getResource();
+                ArtifactOrigin origin = new ArtifactOrigin(artifact, resource.isLocal(), resource.getName());
                 if (listener != null) {
                     listener.startArtifactDownload(this, artifactRef, artifact, origin);
                 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/EnhancedArtifactDownloadReport.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/EnhancedArtifactDownloadReport.java
index 6c21c03..3d270bf 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/EnhancedArtifactDownloadReport.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/EnhancedArtifactDownloadReport.java
@@ -15,15 +15,12 @@
  */
 package org.gradle.api.internal.artifacts.repositories.cachemanager;
 
-import org.apache.ivy.core.cache.ArtifactOrigin;
 import org.apache.ivy.core.module.descriptor.Artifact;
 import org.apache.ivy.core.report.ArtifactDownloadReport;
 import org.apache.ivy.core.report.DownloadStatus;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactOriginWithMetaData;
 
 public class EnhancedArtifactDownloadReport extends ArtifactDownloadReport {
     private Throwable failure;
-    private ArtifactOriginWithMetaData artifactOrigin;
 
     public EnhancedArtifactDownloadReport(Artifact artifact) {
         super(artifact);
@@ -33,21 +30,6 @@ public class EnhancedArtifactDownloadReport extends ArtifactDownloadReport {
         return failure;
     }
 
-    @Override
-    public ArtifactOriginWithMetaData getArtifactOrigin() {
-        return artifactOrigin;
-    }
-
-    @Override
-    public void setArtifactOrigin(ArtifactOrigin origin) {
-        if (origin instanceof ArtifactOriginWithMetaData) {
-            artifactOrigin = (ArtifactOriginWithMetaData) origin;
-            super.setArtifactOrigin(origin);
-        } else {
-            throw new IllegalArgumentException();
-        }
-    }
-
     public void failed(Throwable throwable) {
         setDownloadStatus(DownloadStatus.FAILED);
         setDownloadDetails(throwable.getMessage());
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/LocalFileRepositoryCacheManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/LocalFileRepositoryCacheManager.java
index ca0fe6c..5ac65cd 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/LocalFileRepositoryCacheManager.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/LocalFileRepositoryCacheManager.java
@@ -29,8 +29,6 @@ import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
 import org.apache.ivy.plugins.repository.ResourceDownloader;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.apache.ivy.plugins.resolver.util.ResolvedResource;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactOriginWithMetaData;
-import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData;
 
 import java.io.File;
 import java.text.ParseException;
@@ -58,7 +56,7 @@ public class LocalFileRepositoryCacheManager extends AbstractRepositoryCacheMana
         File file = new File(resolvedResource.getResource().getName());
         assert file.isFile();
 
-        ArtifactOrigin origin = new ArtifactOriginWithMetaData(artifact, true, new DefaultExternalResourceMetaData(file.getAbsolutePath()));
+        ArtifactOrigin origin = new ArtifactOrigin(artifact, true, file.getAbsolutePath());
         report.setDownloadStatus(DownloadStatus.NO);
         report.setArtifactOrigin(origin);
         report.setSize(file.length());
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/PatternRepositoryLayout.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/PatternRepositoryLayout.java
index 1279f36..52cc63a 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/PatternRepositoryLayout.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/PatternRepositoryLayout.java
@@ -22,12 +22,14 @@ import java.util.LinkedHashSet;
 import java.util.Set;
 
 /**
- * A Repository Layout that uses user-supplied patterns. Each pattern will be appended to the base URI for the repository.
- * At least one artifact pattern must be specified. If no ivy patterns are specified, then the artifact patterns will be used.
+ * A repository layout that uses user-supplied patterns. Each pattern will be appended to the base URI for the repository.
+ * At least one artifact pattern must be specified. If no Ivy patterns are specified, then the artifact patterns will be used.
+ * Optionally supports a Maven style layout for the 'organisation' part, replacing any dots with forward slashes.
  */
 public class PatternRepositoryLayout extends RepositoryLayout {
     private final Set<String> artifactPatterns = new LinkedHashSet<String>();
     private final Set<String> ivyPatterns = new LinkedHashSet<String>();
+    private boolean m2compatible;
 
     /**
      * Adds an Ivy artifact pattern to define where artifacts are located in this repository.
@@ -45,6 +47,24 @@ public class PatternRepositoryLayout extends RepositoryLayout {
         ivyPatterns.add(pattern);
     }
 
+    /**
+     * Tells whether a Maven style layout is to be used for the 'organisation' part, replacing any dots with forward slashes.
+     * Defaults to {@code false}.
+     */
+    public boolean getM2Compatible() {
+        return m2compatible;
+    }
+
+    /**
+     * Sets whether a Maven style layout is to be used for the 'organisation' part, replacing any dots with forward slashes.
+     * Defaults to {@code false}.
+     *
+     * @param m2compatible whether a Maven style layout is to be used for the 'organisation' part
+     */
+    public void setM2compatible(boolean m2compatible) {
+        this.m2compatible = m2compatible;
+    }
+
     @Override
     public void apply(URI baseUri, PatternBasedResolver resolver) {
         if (baseUri == null) {
@@ -59,5 +79,7 @@ public class PatternRepositoryLayout extends RepositoryLayout {
         for (String ivyPattern : usedIvyPatterns) {
             resolver.addDescriptorLocation(baseUri, ivyPattern);
         }
+
+        resolver.setM2compatible(m2compatible);
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolver.java
index 66ca797..6be0379 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolver.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal.artifacts.repositories.resolver;
 
 import org.apache.ivy.core.cache.ArtifactOrigin;
 import org.apache.ivy.core.cache.CacheDownloadOptions;
+import org.apache.ivy.core.cache.CacheMetadataOptions;
 import org.apache.ivy.core.cache.RepositoryCacheManager;
 import org.apache.ivy.core.module.descriptor.*;
 import org.apache.ivy.core.module.id.ArtifactRevisionId;
@@ -33,18 +34,28 @@ import org.apache.ivy.core.resolve.ResolvedModuleRevision;
 import org.apache.ivy.core.search.ModuleEntry;
 import org.apache.ivy.core.search.OrganisationEntry;
 import org.apache.ivy.core.search.RevisionEntry;
+import org.apache.ivy.plugins.latest.LatestStrategy;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.namespace.Namespace;
 import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
 import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
 import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
 import org.apache.ivy.plugins.repository.Resource;
 import org.apache.ivy.plugins.repository.ResourceDownloader;
 import org.apache.ivy.plugins.resolver.BasicResolver;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.ResolverSettings;
 import org.apache.ivy.plugins.resolver.util.MDResolvedResource;
 import org.apache.ivy.plugins.resolver.util.ResolvedResource;
 import org.apache.ivy.plugins.resolver.util.ResourceMDParser;
 import org.apache.ivy.plugins.version.VersionMatcher;
+import org.apache.ivy.util.ChecksumHelper;
 import org.apache.ivy.util.Message;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactOriginWithMetaData;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionDescriptor;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
 import org.gradle.api.internal.artifacts.repositories.cachemanager.EnhancedArtifactDownloadReport;
 import org.gradle.api.internal.externalresource.ExternalResource;
 import org.gradle.api.internal.externalresource.MetaDataOnlyExternalResource;
@@ -54,6 +65,7 @@ import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFi
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
 import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository;
 import org.gradle.api.internal.resource.ResourceNotFoundException;
+import org.gradle.util.GFileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -62,18 +74,32 @@ import java.io.IOException;
 import java.text.ParseException;
 import java.util.*;
 
-public class ExternalResourceResolver extends BasicResolver {
+public class ExternalResourceResolver implements DependencyResolver {
     private static final Logger LOGGER = LoggerFactory.getLogger(ExternalResourceResolver.class);
 
     private List<String> ivyPatterns = new ArrayList<String>();
     private List<String> artifactPatterns = new ArrayList<String>();
     private boolean m2compatible;
+    private boolean checkconsistency = true;
+    private boolean allownomd = true;
+    private boolean force;
+    private String checksums;
+    private String name;
+    private ResolverSettings settings;
+    private LatestStrategy latestStrategy;
+    private String latestStrategyName;
+    private String cacheManagerName;
+    private RepositoryCacheManager repositoryCacheManager;
+    private String changingMatcherName;
+    private String changingPattern;
+    private Boolean checkmodified;
+
     private final ExternalResourceRepository repository;
     private final LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder;
     protected VersionLister versionLister;
     private ArtifactResourceResolver artifactResourceResolver = new ArtifactResourceResolver() {
         public ResolvedResource resolve(Artifact artifact) {
-            return getArtifactRef(toSystem(artifact), null);
+            return getArtifactRef(artifact, null);
         }
     };
     private final ResourceDownloader resourceDownloader = new ResourceDownloader() {
@@ -87,97 +113,104 @@ public class ExternalResourceResolver extends BasicResolver {
                                     VersionLister versionLister,
                                     LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder
     ) {
-        setName(name);
+        this.name = name;
         this.versionLister = versionLister;
         this.repository = repository;
         this.locallyAvailableResourceFinder = locallyAvailableResourceFinder;
     }
 
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+    public void setSettings(ResolverSettings ivy) {
+        this.settings = ivy;
+    }
+
+    public ResolverSettings getSettings() {
+        return settings;
+    }
+
     protected ExternalResourceRepository getRepository() {
         return repository;
     }
 
-    public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data)
-            throws ParseException {
-        DependencyDescriptor systemDd = dd;
-        DependencyDescriptor nsDd = fromSystem(dd);
+    public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data) throws ParseException {
+        // This is not used
+        throw new UnsupportedOperationException();
+    }
 
-        ModuleRevisionId systemMrid = systemDd.getDependencyRevisionId();
-        ModuleRevisionId nsMrid = nsDd.getDependencyRevisionId();
+    public void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
+        ModuleRevisionId nsMrid = dependencyDescriptor.getDependencyRevisionId();
 
-        boolean isDynamic = getSettings().getVersionMatcher().isDynamic(systemMrid);
-        ResolvedModuleRevision rmr = null;
+        boolean isDynamic = getVersionMatcher().isDynamic(nsMrid);
 
-        ResolvedResource ivyRef = findIvyFileRef(nsDd, data);
+        ResolvedResource ivyRef = findIvyFileRef(dependencyDescriptor);
 
         // get module descriptor
         ModuleDescriptor nsMd;
-        ModuleDescriptor systemMd;
         if (ivyRef == null) {
             if (!isAllownomd()) {
-                LOGGER.debug("No ivy file found for {}", systemMrid);
-                return null;
+                LOGGER.debug("No ivy file found for module '{}' in repository '{}'.", nsMrid, getName());
+                result.missing();
+                return;
             }
-            nsMd = DefaultModuleDescriptor.newDefaultInstance(nsMrid, nsDd
-                    .getAllDependencyArtifacts());
-            ResolvedResource artifactRef = findFirstArtifactRef(nsMd, nsDd, data);
+            nsMd = DefaultModuleDescriptor.newDefaultInstance(nsMrid, dependencyDescriptor.getAllDependencyArtifacts());
+            ResolvedResource artifactRef = findFirstArtifactRef(nsMd);
             if (artifactRef == null) {
-                LOGGER.debug("No ivy file nor artifact found for {}", systemMrid);
-                return null;
+                LOGGER.debug("No ivy file nor artifact found for module '{}' in repository '{}'.", nsMrid, getName());
+                result.missing();
             } else {
                 long lastModified = artifactRef.getLastModified();
                 if (lastModified != 0 && nsMd instanceof DefaultModuleDescriptor) {
                     ((DefaultModuleDescriptor) nsMd).setLastModified(lastModified);
                 }
-                Message.verbose("\t" + getName() + ": no ivy file found for " + systemMrid
-                        + ": using default data");
+                LOGGER.debug("No ivy file found for module '{}' in repository '{}', using default data instead.", nsMrid, getName());
                 if (isDynamic) {
-                    nsMd.setResolvedModuleRevisionId(ModuleRevisionId.newInstance(nsMrid,
-                            artifactRef.getRevision()));
+                    nsMd.setResolvedModuleRevisionId(ModuleRevisionId.newInstance(nsMrid, artifactRef.getRevision()));
                 }
-                systemMd = toSystem(nsMd);
-                MetadataArtifactDownloadReport madr =
-                        new MetadataArtifactDownloadReport(systemMd.getMetadataArtifact());
-                madr.setDownloadStatus(DownloadStatus.NO);
-                madr.setSearched(true);
-                rmr = new ResolvedModuleRevision(this, this, systemMd, madr, isForce());
+                result.resolved(nsMd, isChanging(nsMd), null);
             }
         } else {
-            if (ivyRef instanceof MDResolvedResource) {
-                rmr = ((MDResolvedResource) ivyRef).getResolvedModuleRevision();
-            }
-            if (rmr == null) {
-                rmr = parse(ivyRef, systemDd, data);
-            }
-            if (!rmr.getReport().isDownloaded()
-                    && rmr.getReport().getLocalFile() != null) {
-                return rmr;
-            } else {
+            try {
+                ResolvedModuleRevision rmr = null;
+                if (ivyRef instanceof MDResolvedResource) {
+                    rmr = ((MDResolvedResource) ivyRef).getResolvedModuleRevision();
+                }
+                if (rmr == null) {
+                    rmr = parse(ivyRef, dependencyDescriptor);
+                }
+
                 nsMd = rmr.getDescriptor();
 
                 // check descriptor data is in sync with resource revision and names
-                systemMd = toSystem(nsMd);
                 if (isCheckconsistency()) {
-                    checkDescriptorConsistency(systemMrid, systemMd, ivyRef);
                     checkDescriptorConsistency(nsMrid, nsMd, ivyRef);
                 }
-                rmr = new ResolvedModuleRevision(
-                        this, this, systemMd, toSystem(rmr.getReport()), isForce());
+                LOGGER.debug("Ivy file found for module '{}' in repository '{}'.", nsMrid, getName());
+                result.resolved(nsMd, isChanging(nsMd), null);
+            } catch (ParseException e) {
+                result.failed(new ModuleVersionResolveException(nsMrid, e));
             }
         }
-
-        return rmr;
     }
 
-    public ResolvedModuleRevision parse(final ResolvedResource mdRef, DependencyDescriptor dd,
-            ResolveData data) throws ParseException {
-
-        DependencyDescriptor nsDd = dd;
-        dd = toSystem(nsDd);
+    protected VersionMatcher getVersionMatcher() {
+        return getSettings().getVersionMatcher();
+    }
 
+    private ResolvedModuleRevision parse(final ResolvedResource mdRef, DependencyDescriptor dd) throws ParseException {
+        //TODO: check why we don't use our own ParserRegistry here.
         ModuleRevisionId mrid = dd.getDependencyRevisionId();
-        ModuleDescriptorParser parser = ModuleDescriptorParserRegistry
-                .getInstance().getParser(mdRef.getResource());
+        ModuleDescriptorParser parser = ModuleDescriptorParserRegistry.getInstance().getParser(mdRef.getResource());
         if (parser == null) {
             throw new RuntimeException("no module descriptor parser available for " + mdRef.getResource());
         }
@@ -185,16 +218,16 @@ public class ExternalResourceResolver extends BasicResolver {
         ModuleRevisionId resolvedMrid = mrid;
 
         // first check if this dependency has not yet been resolved
-        if (getSettings().getVersionMatcher().isDynamic(mrid)) {
+        if (getVersionMatcher().isDynamic(mrid)) {
             resolvedMrid = ModuleRevisionId.newInstance(mrid, mdRef.getRevision());
         }
 
         Artifact moduleArtifact = parser.getMetadataArtifact(resolvedMrid, mdRef.getResource());
-        return getRepositoryCacheManager().cacheModuleDescriptor(this, mdRef, dd, moduleArtifact, resourceDownloader, getCacheOptions(data));
+        return getRepositoryCacheManager().cacheModuleDescriptor(this, mdRef, dd, moduleArtifact, resourceDownloader, new CacheMetadataOptions());
     }
 
     private void checkDescriptorConsistency(ModuleRevisionId mrid, ModuleDescriptor md,
-            ResolvedResource ivyRef) throws ParseException {
+                                            ResolvedResource ivyRef) throws ParseException {
         boolean ok = true;
         StringBuilder errors = new StringBuilder();
         if (!mrid.getOrganisation().equals(md.getModuleRevisionId().getOrganisation())) {
@@ -225,7 +258,7 @@ public class ExternalResourceResolver extends BasicResolver {
         if (ivyRef.getRevision() != null && !ivyRef.getRevision().startsWith("working@")) {
             ModuleRevisionId expectedMrid = ModuleRevisionId
                     .newInstance(mrid, ivyRef.getRevision());
-            if (!getSettings().getVersionMatcher().accept(expectedMrid, md)) {
+            if (!getVersionMatcher().accept(expectedMrid, md)) {
                 Message.error("\t" + getName() + ": bad revision found in " + ivyRef.getResource()
                         + ": expected='" + ivyRef.getRevision() + " found='"
                         + md.getModuleRevisionId().getRevision() + "'");
@@ -240,18 +273,6 @@ public class ExternalResourceResolver extends BasicResolver {
             errors.append("bad status: '" + md.getStatus() + "'; ");
             ok = false;
         }
-        for (Iterator it = mrid.getExtraAttributes().entrySet().iterator(); it.hasNext();) {
-            Map.Entry extra = (Map.Entry) it.next();
-            if (extra.getValue() != null && !extra.getValue().equals(
-                                                md.getExtraAttribute((String) extra.getKey()))) {
-                String errorMsg = "bad " + extra.getKey() + " found in " + ivyRef.getResource()
-                                        + ": expected='" + extra.getValue() + "' found='"
-                                        + md.getExtraAttribute((String) extra.getKey()) + "'";
-                Message.error("\t" + getName() + ": " + errorMsg);
-                errors.append(errorMsg + ";");
-                ok = false;
-            }
-        }
         if (!ok) {
             throw new ParseException("inconsistent module descriptor file found in '"
                     + ivyRef.getResource() + "': " + errors, 0);
@@ -259,16 +280,20 @@ public class ExternalResourceResolver extends BasicResolver {
     }
 
     public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
+        // This is not used
+        throw new UnsupportedOperationException();
+    }
+
+    public ResolvedResource findIvyFileRef(DependencyDescriptor dd) {
         ModuleRevisionId mrid = dd.getDependencyRevisionId();
-        return findResourceUsingPatterns(mrid, ivyPatterns, DefaultArtifact.newIvyArtifact(mrid, data.getDate()), getRMDParser(dd, data), data.getDate(), true);
+        Artifact artifact = DefaultArtifact.newIvyArtifact(mrid, null);
+        return findResourceUsingPatterns(mrid, ivyPatterns, artifact, getRMDParser(dd), null, true);
     }
 
-    @Override
-    protected ResolvedResource findFirstArtifactRef(ModuleDescriptor md, DependencyDescriptor dd,
-                                                    ResolveData data) {
+    protected ResolvedResource findFirstArtifactRef(ModuleDescriptor md) {
         for (String configuration : md.getConfigurationsNames()) {
             for (Artifact artifact : md.getArtifacts(configuration)) {
-                ResolvedResource artifactRef = getArtifactRef(artifact, data.getDate(), false);
+                ResolvedResource artifactRef = getArtifactRef(artifact, null, false);
                 if (artifactRef != null) {
                     return artifactRef;
                 }
@@ -277,40 +302,56 @@ public class ExternalResourceResolver extends BasicResolver {
         return null;
     }
 
-    @Override
     public boolean exists(Artifact artifact) {
         // This is never used
         throw new UnsupportedOperationException();
     }
 
-    @Override
     public ArtifactOrigin locate(Artifact artifact) {
         ResolvedResource artifactRef = getArtifactRef(artifact, null, false);
         if (artifactRef != null && artifactRef.getResource().exists()) {
-            return new ArtifactOriginWithMetaData(artifact, artifactRef.getResource());
+            final Resource resource = artifactRef.getResource();
+            return new ArtifactOrigin(artifact, resource.isLocal(), resource.getName());
         }
         return null;
     }
 
-    @Override
     protected ResolvedResource getArtifactRef(Artifact artifact, Date date) {
         return getArtifactRef(artifact, date, true);
     }
 
     protected ResolvedResource getArtifactRef(Artifact artifact, Date date, boolean forDownload) {
-        ModuleRevisionId mrid = artifact.getModuleRevisionId();
-        return findResourceUsingPatterns(mrid, artifactPatterns, artifact,
-                getDefaultRMDParser(artifact.getModuleRevisionId().getModuleId()), date, forDownload);
+        ModuleRevisionId moduleRevisionId = artifact.getModuleRevisionId();
+        ResourceMDParser parser = getDefaultRMDParser(artifact.getModuleRevisionId().getModuleId());
+        return findResourceUsingPatterns(moduleRevisionId, getArtifactPatterns(), artifact, parser, date, forDownload);
+    }
+
+    protected ResourceMDParser getRMDParser(final DependencyDescriptor dd) {
+        return new ResourceMDParser() {
+            public MDResolvedResource parse(Resource resource, String rev) {
+                try {
+                    ResolvedModuleRevision rmr = ExternalResourceResolver.this.parse(new ResolvedResource(resource, rev), dd);
+                    if (rmr == null) {
+                        return null;
+                    } else {
+                        return new MDResolvedResource(resource, rev, rmr);
+                    }
+                } catch (ParseException e) {
+                    Message.warn("Failed to parse the file '" + resource + "': "
+                            + e.getMessage());
+                    return null;
+                }
+            }
+
+        };
     }
 
     protected ResourceMDParser getDefaultRMDParser(final ModuleId mid) {
         return new ResourceMDParser() {
             public MDResolvedResource parse(Resource resource, String rev) {
-                DefaultModuleDescriptor md =
-                    DefaultModuleDescriptor.newDefaultInstance(new ModuleRevisionId(mid, rev));
+                DefaultModuleDescriptor md = DefaultModuleDescriptor.newDefaultInstance(new ModuleRevisionId(mid, rev));
                 md.setStatus("integration");
-                MetadataArtifactDownloadReport madr =
-                    new MetadataArtifactDownloadReport(md.getMetadataArtifact());
+                MetadataArtifactDownloadReport madr = new MetadataArtifactDownloadReport(md.getMetadataArtifact());
                 madr.setDownloadStatus(DownloadStatus.NO);
                 madr.setSearched(true);
                 return new MDResolvedResource(resource, rev, new ResolvedModuleRevision(ExternalResourceResolver.this, ExternalResourceResolver.this, md, madr, isForce()));
@@ -321,7 +362,7 @@ public class ExternalResourceResolver extends BasicResolver {
     protected ResolvedResource findResourceUsingPatterns(ModuleRevisionId moduleRevision, List<String> patternList, Artifact artifact, ResourceMDParser rmdparser, Date date, boolean forDownload) {
         List<ResolvedResource> resolvedResources = new ArrayList<ResolvedResource>();
         Set<String> foundRevisions = new HashSet<String>();
-        boolean dynamic = getSettings().getVersionMatcher().isDynamic(moduleRevision);
+        boolean dynamic = getVersionMatcher().isDynamic(moduleRevision);
         for (String pattern : patternList) {
             ResourcePattern resourcePattern = toResourcePattern(pattern);
             ResolvedResource rres = findResourceUsingPattern(moduleRevision, resourcePattern, artifact, rmdparser, date, forDownload);
@@ -353,7 +394,7 @@ public class ExternalResourceResolver extends BasicResolver {
 
     public ResolvedResource findLatestResource(ModuleRevisionId mrid, VersionList versions, ResourceMDParser rmdparser, Date date, ResourcePattern pattern, Artifact artifact, boolean forDownload) {
         String name = getName();
-        VersionMatcher versionMatcher = getSettings().getVersionMatcher();
+        VersionMatcher versionMatcher = getVersionMatcher();
         List<String> sorted = versions.sortLatestFirst(getLatestStrategy());
         for (String version : sorted) {
             ModuleRevisionId foundMrid = ModuleRevisionId.newInstance(mrid, version);
@@ -400,7 +441,7 @@ public class ExternalResourceResolver extends BasicResolver {
     }
 
     protected ResolvedResource findResourceUsingPattern(ModuleRevisionId moduleRevisionId, ResourcePattern pattern, Artifact artifact, ResourceMDParser resourceParser, Date date, boolean forDownload) {
-        VersionMatcher versionMatcher = getSettings().getVersionMatcher();
+        VersionMatcher versionMatcher = getVersionMatcher();
         if (!versionMatcher.isDynamic(moduleRevisionId)) {
             return findStaticResourceUsingPattern(moduleRevisionId, pattern, artifact, forDownload);
         } else {
@@ -440,71 +481,85 @@ public class ExternalResourceResolver extends BasicResolver {
         }
     }
 
+    public void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource) {
+        EnhancedArtifactDownloadReport artifactDownloadReport = download(artifact, moduleSource);
+        if (downloadFailed(artifactDownloadReport)) {
+            result.failed(new ArtifactResolveException(artifactDownloadReport.getArtifact(), artifactDownloadReport.getFailure()));
+            return;
+        }
+        File localFile = artifactDownloadReport.getLocalFile();
+        if (localFile != null) {
+            result.resolved(localFile);
+        } else {
+            result.notFound(artifact);
+        }
+    }
+
+    protected EnhancedArtifactDownloadReport download(Artifact artifact, ModuleSource moduleSource) {
+        return download(artifact);
+    }
+
     public EnhancedArtifactDownloadReport download(Artifact artifact) {
         RepositoryCacheManager cacheManager = getRepositoryCacheManager();
+
         return (EnhancedArtifactDownloadReport) cacheManager.download(artifact, artifactResourceResolver, resourceDownloader, new CacheDownloadOptions());
     }
 
-    @Override
     public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
         // This is never used
         throw new UnsupportedOperationException();
     }
 
-    @Override
     public ArtifactDownloadReport download(ArtifactOrigin origin, DownloadOptions options) {
         // This is never used
         throw new UnsupportedOperationException();
     }
 
-    @Override
     public void reportFailure() {
         // This is never used
         throw new UnsupportedOperationException();
     }
 
-    @Override
     public void reportFailure(Artifact art) {
         // This is never used
         throw new UnsupportedOperationException();
     }
 
-    @Override
     public String[] listTokenValues(String token, Map otherTokenValues) {
         // This is never used
         throw new UnsupportedOperationException();
     }
 
-    @Override
     public Map[] listTokenValues(String[] tokens, Map criteria) {
         // This is never used
         throw new UnsupportedOperationException();
     }
 
-    @Override
     public OrganisationEntry[] listOrganisations() {
         // This is never used
         throw new UnsupportedOperationException();
     }
 
-    @Override
     public ModuleEntry[] listModules(OrganisationEntry org) {
         // This is never used
         throw new UnsupportedOperationException();
     }
 
-    @Override
     public RevisionEntry[] listRevisions(ModuleEntry mod) {
         // This is never used
         throw new UnsupportedOperationException();
     }
 
-    protected ResolvedResource findArtifactRef(Artifact artifact, Date date) {
-        // This is never used
-        throw new UnsupportedOperationException();
+    public void abortPublishTransaction() throws IOException {
+    }
+
+    public void beginPublishTransaction(ModuleRevisionId module, boolean overwrite) throws IOException {
     }
 
-    protected Resource getResource(String source) throws IOException {
+    public void commitPublishTransaction() throws IOException {
+    }
+
+    public Namespace getNamespace() {
         // This is never used
         throw new UnsupportedOperationException();
     }
@@ -540,7 +595,7 @@ public class ExternalResourceResolver extends BasicResolver {
     protected long get(Resource resource, File destination) throws IOException {
         LOGGER.debug("Downloading {} to {}", resource.getName(), destination);
         if (destination.getParentFile() != null) {
-            destination.getParentFile().mkdirs();
+            GFileUtils.mkdirs(destination.getParentFile());
         }
 
         if (!(resource instanceof ExternalResource)) {
@@ -556,6 +611,38 @@ public class ExternalResourceResolver extends BasicResolver {
         return destination.length();
     }
 
+    private long getAndCheck(Resource resource, File dest) throws IOException {
+        long size = get(resource, dest);
+        String[] checksums = getChecksumAlgorithms();
+        boolean checked = false;
+        for (int i = 0; i < checksums.length && !checked; i++) {
+            checked = check(resource, dest, checksums[i]);
+        }
+        return size;
+    }
+
+    private boolean check(Resource resource, File dest, String algorithm) throws IOException {
+        if (!ChecksumHelper.isKnownAlgorithm(algorithm)) {
+            throw new IllegalArgumentException("Unknown checksum algorithm: " + algorithm);
+        }
+
+        ExternalResource checksumResource = repository.getResource(resource.getName() + "." + algorithm);
+        if (checksumResource != null && checksumResource.exists()) {
+            LOGGER.debug(algorithm + " file found for " + resource + ": checking...");
+            File csFile = File.createTempFile("ivytmp", algorithm);
+            try {
+                get(checksumResource, csFile);
+                ChecksumHelper.check(dest, csFile, algorithm);
+                Message.verbose(algorithm + " OK for " + resource);
+                return true;
+            } finally {
+                csFile.delete();
+            }
+        } else {
+            return false;
+        }
+    }
+
     public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
         String destinationPattern;
         if ("ivy".equals(artifact.getType()) && !getIvyPatterns().isEmpty()) {
@@ -568,7 +655,7 @@ public class ExternalResourceResolver extends BasicResolver {
         String destination = toResourcePattern(destinationPattern).toPath(artifact);
 
         put(src, destination);
-        LOGGER.info("Published {} to {}", artifact.getName(), hidePassword(destination));
+        LOGGER.info("Published {} to {}", artifact.getName(), destination);
     }
 
     private void put(File src, String destination) throws IOException {
@@ -581,10 +668,6 @@ public class ExternalResourceResolver extends BasicResolver {
         repository.put(src, destination);
     }
 
-    protected Collection findNames(Map tokenValues, String token) {
-        throw new UnsupportedOperationException();
-    }
-
     public void addIvyPattern(String pattern) {
         ivyPatterns.add(pattern);
     }
@@ -610,17 +693,8 @@ public class ExternalResourceResolver extends BasicResolver {
     }
 
     public void dumpSettings() {
-        super.dumpSettings();
-        Message.debug("\t\tm2compatible: " + isM2compatible());
-        Message.debug("\t\tivy patterns:");
-        for (String p : getIvyPatterns()) {
-            Message.debug("\t\t\t" + p);
-        }
-        Message.debug("\t\tartifact patterns:");
-        for (String p : getArtifactPatterns()) {
-            Message.debug("\t\t\t" + p);
-        }
-        Message.debug("\t\trepository: " + repository);
+        // this is not used
+        throw new UnsupportedOperationException();
     }
 
     public boolean isM2compatible() {
@@ -631,7 +705,164 @@ public class ExternalResourceResolver extends BasicResolver {
         m2compatible = compatible;
     }
 
+    public boolean isCheckconsistency() {
+        return checkconsistency;
+    }
+
+    public void setCheckconsistency(boolean checkConsistency) {
+        checkconsistency = checkConsistency;
+    }
+
+    public void setForce(boolean force) {
+        this.force = force;
+    }
+
+    public boolean isForce() {
+        return force;
+    }
+
+    public boolean isAllownomd() {
+        return allownomd;
+    }
+
+    public void setAllownomd(boolean b) {
+        Message.deprecated(
+            "allownomd is deprecated, please use descriptor=\""
+            + (b ? BasicResolver.DESCRIPTOR_OPTIONAL : BasicResolver.DESCRIPTOR_REQUIRED) + "\" instead");
+        allownomd = b;
+    }
+
+    /**
+     * Sets the module descriptor presence rule.
+     * Should be one of {@link BasicResolver#DESCRIPTOR_REQUIRED} or {@link BasicResolver#DESCRIPTOR_OPTIONAL}.
+     *
+     * @param descriptorRule the descriptor rule to use with this resolver.
+     */
+    public void setDescriptor(String descriptorRule) {
+        if (BasicResolver.DESCRIPTOR_REQUIRED.equals(descriptorRule)) {
+          allownomd = false;
+        } else if (BasicResolver.DESCRIPTOR_OPTIONAL.equals(descriptorRule)) {
+          allownomd = true;
+        } else {
+            throw new IllegalArgumentException(
+                "unknown descriptor rule '" + descriptorRule
+                + "'. Allowed rules are: "
+                + Arrays.asList(new String[] {BasicResolver.DESCRIPTOR_REQUIRED, BasicResolver.DESCRIPTOR_OPTIONAL}));
+        }
+    }
+
+    public String[] getChecksumAlgorithms() {
+        if (checksums == null) {
+            return new String[0];
+        }
+        // csDef is a comma separated list of checksum algorithms to use with this resolver
+        // we parse and return it as a String[]
+        String[] checksums = this.checksums.split(",");
+        List<String> algos = new ArrayList<String>();
+        for (int i = 0; i < checksums.length; i++) {
+            String cs = checksums[i].trim();
+            if (!"".equals(cs) && !"none".equals(cs)) {
+                algos.add(cs);
+            }
+        }
+        return algos.toArray(new String[algos.size()]);
+    }
+
+    public void setChecksums(String checksums) {
+        this.checksums = checksums;
+    }
+
+    public LatestStrategy getLatestStrategy() {
+        if (latestStrategy == null) {
+            initLatestStrategyFromSettings();
+        }
+        return latestStrategy;
+    }
+
+    private void initLatestStrategyFromSettings() {
+        if (getSettings() != null) {
+            if (latestStrategyName != null && !"default".equals(latestStrategyName)) {
+                latestStrategy = getSettings().getLatestStrategy(latestStrategyName);
+                if (latestStrategy == null) {
+                    throw new IllegalStateException(
+                        "unknown latest strategy '" + latestStrategyName + "'");
+                }
+            } else {
+                latestStrategy = getSettings().getDefaultLatestStrategy();
+                Message.debug(getName() + ": no latest strategy defined: using default");
+            }
+        } else {
+            throw new IllegalStateException(
+                "no ivy instance found: "
+                + "impossible to get a latest strategy without ivy instance");
+        }
+    }
+
+    public void setLatestStrategy(LatestStrategy latestStrategy) {
+        this.latestStrategy = latestStrategy;
+    }
+
+    public void setLatest(String strategyName) {
+        latestStrategyName = strategyName;
+    }
+
+    public String getLatest() {
+        if (latestStrategyName == null) {
+            latestStrategyName = "default";
+        }
+        return latestStrategyName;
+    }
+
+    public void setChangingMatcher(String changingMatcherName) {
+        this.changingMatcherName = changingMatcherName;
+    }
+
+    protected String getChangingMatcherName() {
+        return changingMatcherName;
+    }
+
+    public void setChangingPattern(String changingPattern) {
+        this.changingPattern = changingPattern;
+    }
+
+    protected String getChangingPattern() {
+        return changingPattern;
+    }
+
+    public void setCheckmodified(boolean check) {
+        checkmodified = Boolean.valueOf(check);
+    }
+
+    public RepositoryCacheManager getRepositoryCacheManager() {
+        return repositoryCacheManager;
+    }
+
+    public void setRepositoryCacheManager(RepositoryCacheManager repositoryCacheManager) {
+        this.repositoryCacheManager = repositoryCacheManager;
+    }
+
+    public void setCache(String cacheName) {
+        cacheManagerName = cacheName;
+    }
+
     protected ResourcePattern toResourcePattern(String pattern) {
         return isM2compatible() ? new M2ResourcePattern(pattern) : new IvyResourcePattern(pattern);
     }
+
+    protected boolean downloadFailed(ArtifactDownloadReport artifactReport) {
+        return artifactReport.getDownloadStatus() == DownloadStatus.FAILED
+                && !artifactReport.getDownloadDetails().equals(ArtifactDownloadReport.MISSING_ARTIFACT);
+    }
+
+    private boolean isChanging(ModuleDescriptor moduleDescriptor) {
+        if (changingMatcherName == null || changingPattern == null) {
+            return false;
+        }
+        PatternMatcher matcher = settings.getMatcher(changingMatcherName);
+        if (matcher == null) {
+            throw new IllegalStateException("unknown matcher '" + changingMatcherName
+                    + "'. It is set as changing matcher in " + this);
+        }
+        return matcher.getMatcher(changingPattern).matches(moduleDescriptor.getResolvedModuleRevisionId().getRevision());
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/M2ResourcePattern.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/M2ResourcePattern.java
index dd53162..4233111 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/M2ResourcePattern.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/M2ResourcePattern.java
@@ -42,6 +42,17 @@ public class M2ResourcePattern extends IvyResourcePattern {
     }
 
     @Override
+    public String toPath(Artifact artifact) {
+        Map<String, Object> attributes = toAttributes(artifact);
+        if (artifact.getModuleRevisionId().getExtraAttributes().containsKey("timestamp")) {
+            final Object revisionValue = artifact.getModuleRevisionId().getExtraAttribute("timestamp");
+            String pattern = getPattern().replaceFirst("\\-\\[revision\\]", "-" + revisionValue);
+            return IvyPatternHelper.substituteTokens(pattern, attributes);
+        }
+        return IvyPatternHelper.substituteTokens(getPattern(), attributes);
+    }
+
+    @Override
     public String toModuleVersionPath(Artifact artifact) {
         String pattern = getPattern();
         if (!pattern.endsWith(MavenPattern.M2_PATTERN)) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenMetadataLoader.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenMetadataLoader.java
index 94fa481..d4cb7d6 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenMetadataLoader.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenMetadataLoader.java
@@ -44,7 +44,7 @@ class MavenMetadataLoader {
         MavenMetadata metadata = new MavenMetadata();
         try {
             parseMavenMetadataInfo(metadataLocation, metadata);
-        } catch (ResourceException e) {
+        } catch (ResourceNotFoundException e) {
             throw e;
         } catch (Exception e) {
             throw new ResourceException(String.format("Unable to load Maven meta-data from %s.", metadataLocation), e);
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenResolver.java
index 0b774a8..b80d0d3 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenResolver.java
@@ -20,24 +20,23 @@ import org.apache.ivy.core.module.descriptor.DefaultArtifact;
 import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.apache.ivy.core.module.id.ArtifactRevisionId;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.ivy.core.resolve.ResolveData;
 import org.apache.ivy.plugins.matcher.PatternMatcher;
 import org.apache.ivy.plugins.resolver.util.ResolvedResource;
 import org.apache.ivy.plugins.resolver.util.ResourceMDParser;
-import org.apache.ivy.util.Message;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionDescriptor;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
+import org.gradle.api.internal.artifacts.repositories.cachemanager.EnhancedArtifactDownloadReport;
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
 import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
 import org.gradle.api.internal.resource.ResourceNotFoundException;
 import org.gradle.api.resources.ResourceException;
 import org.gradle.util.DeprecationLogger;
+import org.gradle.util.WrapUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.net.URI;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
 
 public class MavenResolver extends ExternalResourceResolver implements PatternBasedResolver {
     private static final Logger LOGGER = LoggerFactory.getLogger(MavenResolver.class);
@@ -61,7 +60,6 @@ public class MavenResolver extends ExternalResourceResolver implements PatternBa
         this.transport = transport;
         this.root = transport.convertToPath(rootUri);
 
-        setDescriptor(DESCRIPTOR_OPTIONAL);
         super.setM2compatible(true);
 
         // SNAPSHOT revisions are changing revisions
@@ -71,6 +69,62 @@ public class MavenResolver extends ExternalResourceResolver implements PatternBa
         updatePatterns();
     }
 
+    public void getDependency(DependencyDescriptor dd, BuildableModuleVersionDescriptor result) {
+        if (isSnapshotVersion(dd)) {
+            getSnapshotDependency(dd, result);
+        } else {
+            super.getDependency(dd, result);
+        }
+    }
+
+    private void getSnapshotDependency(DependencyDescriptor dd, BuildableModuleVersionDescriptor result) {
+        final ModuleRevisionId dependencyRevisionId = dd.getDependencyRevisionId();
+        final String uniqueSnapshotVersion = findUniqueSnapshotVersion(dependencyRevisionId);
+        if (uniqueSnapshotVersion != null) {
+            DependencyDescriptor enrichedDependencyDescriptor = enrichDependencyDescriptorWithSnapshotVersionInfo(dd, dependencyRevisionId, uniqueSnapshotVersion);
+            super.getDependency(enrichedDependencyDescriptor, result);
+            if (result.getState() == BuildableModuleVersionDescriptor.State.Resolved) {
+                result.resolved(result.getDescriptor(), result.isChanging(), new TimestampedModuleSource(uniqueSnapshotVersion));
+            }
+        } else {
+            super.getDependency(dd, result);
+        }
+    }
+
+    private DependencyDescriptor enrichDependencyDescriptorWithSnapshotVersionInfo(DependencyDescriptor dd, ModuleRevisionId dependencyRevisionId, String uniqueSnapshotVersion) {
+        Map<String, String> extraAttributes = new HashMap<String, String>(1);
+        extraAttributes.put("timestamp", uniqueSnapshotVersion);
+        final ModuleRevisionId newModuleRevisionId = ModuleRevisionId.newInstance(dependencyRevisionId.getOrganisation(), dependencyRevisionId.getName(), dependencyRevisionId.getRevision(), extraAttributes);
+        return dd.clone(newModuleRevisionId);
+    }
+
+    private boolean isSnapshotVersion(DependencyDescriptor dd) {
+        return dd.getDependencyRevisionId().getRevision().endsWith("SNAPSHOT");
+    }
+
+    protected EnhancedArtifactDownloadReport download(Artifact artifact, ModuleSource moduleSource) {
+        EnhancedArtifactDownloadReport artifactDownloadReport;
+
+        if (moduleSource instanceof TimestampedModuleSource) {
+            TimestampedModuleSource timestampedModuleSource = (TimestampedModuleSource) moduleSource;
+            String timestampedVersion = timestampedModuleSource.getTimestampedVersion();
+            artifactDownloadReport = downloadTimestampedVersion(artifact, timestampedVersion);
+        } else {
+            artifactDownloadReport = download(artifact);
+        }
+        return artifactDownloadReport;
+    }
+
+    private EnhancedArtifactDownloadReport downloadTimestampedVersion(Artifact artifact, String timestampedVersion) {
+        final ModuleRevisionId artifactModuleRevisionId = artifact.getModuleRevisionId();
+        final ModuleRevisionId moduleRevisionId = ModuleRevisionId.newInstance(artifactModuleRevisionId.getOrganisation(),
+                artifactModuleRevisionId.getName(),
+                artifactModuleRevisionId.getRevision(),
+                WrapUtil.toMap("timestamp", timestampedVersion));
+        final Artifact artifactWithResolvedModuleRevisionId = DefaultArtifact.cloneWithAnotherMrid(artifact, moduleRevisionId);
+        return download(artifactWithResolvedModuleRevisionId);
+    }
+
     public void addArtifactLocation(URI baseUri, String pattern) {
         if (pattern != null && pattern.length() > 0) {
             throw new IllegalArgumentException("Maven Resolver only supports a single pattern. It cannot be provided on a per-location basis.");
@@ -92,7 +146,7 @@ public class MavenResolver extends ExternalResourceResolver implements PatternBa
         if (isUsepoms()) {
             setIvyPatterns(Collections.singletonList(getWholePattern()));
         } else {
-            setIvyPatterns(Collections.EMPTY_LIST);
+            setIvyPatterns(Collections.<String>emptyList());
         }
 
         List<String> artifactPatterns = new ArrayList<String>();
@@ -103,63 +157,15 @@ public class MavenResolver extends ExternalResourceResolver implements PatternBa
         setArtifactPatterns(artifactPatterns);
     }
 
-    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
+    public ResolvedResource findIvyFileRef(DependencyDescriptor dd) {
         if (isUsepoms()) {
             ModuleRevisionId moduleRevisionId = dd.getDependencyRevisionId();
-
-            if (moduleRevisionId.getRevision().endsWith("SNAPSHOT")) {
-                ResolvedResource resolvedResource = findSnapshotDescriptor(dd, data, moduleRevisionId, true);
-                if (resolvedResource != null) {
-                    return resolvedResource;
-                }
-            }
-
-            Artifact pomArtifact = DefaultArtifact.newPomArtifact(moduleRevisionId, data.getDate());
-            ResourceMDParser parser = getRMDParser(dd, data);
-            return findResourceUsingPatterns(moduleRevisionId, getIvyPatterns(), pomArtifact, parser, data.getDate(), true);
-        }
-
-        return null;
-    }
-
-    private ResolvedResource findSnapshotDescriptor(DependencyDescriptor dd, ResolveData data, ModuleRevisionId moduleRevisionId, boolean forDownload) {
-        String rev = findUniqueSnapshotVersion(moduleRevisionId);
-        if (rev != null) {
-            // here it would be nice to be able to store the resolved snapshot version, to avoid
-            // having to follow the same process to download artifacts
-            LOGGER.debug("[{}] {}", rev, moduleRevisionId);
-
-            // replace the revision token in file name with the resolved revision
-            String pattern = getWholePattern().replaceFirst("\\-\\[revision\\]", "-" + rev);
-            Artifact pomArtifact = DefaultArtifact.newPomArtifact(moduleRevisionId, data.getDate());
-            ResourcePattern resourcePattern = toResourcePattern(pattern);
-            return findResourceUsingPattern(moduleRevisionId, resourcePattern, pomArtifact, getRMDParser(dd, data), data.getDate(), forDownload);
-        }
-        return null;
-    }
-
-    protected ResolvedResource getArtifactRef(Artifact artifact, Date date, boolean forDownload) {
-        ModuleRevisionId moduleRevisionId = artifact.getModuleRevisionId();
-
-        if (moduleRevisionId.getRevision().endsWith("SNAPSHOT")) {
-            ResolvedResource resolvedResource = findSnapshotArtifact(artifact, date, moduleRevisionId, forDownload);
-            if (resolvedResource != null) {
-                return resolvedResource;
-            }
+            //we might need a own implementation of DefaultArtifact here as there is no way to pass extraAttributes AND isMetaData to DefaultArtifact
+            Artifact pomArtifact = new DefaultArtifact(moduleRevisionId, null, moduleRevisionId.getName(), "pom", "pom", moduleRevisionId.getExtraAttributes());
+            ResourceMDParser parser = getRMDParser(dd);
+            return findResourceUsingPatterns(moduleRevisionId, getIvyPatterns(), pomArtifact, parser, null, true);
         }
-        ResourceMDParser parser = getDefaultRMDParser(artifact.getModuleRevisionId().getModuleId());
-        return findResourceUsingPatterns(moduleRevisionId, getArtifactPatterns(), artifact, parser, date, forDownload);
-    }
 
-    private ResolvedResource findSnapshotArtifact(Artifact artifact, Date date, ModuleRevisionId moduleRevisionId, boolean forDownload) {
-        String rev = findUniqueSnapshotVersion(moduleRevisionId);
-        if (rev != null) {
-            // replace the revision token in file name with the resolved revision
-            // TODO:DAZ We're not using all available artifact patterns here, only the "main" pattern. This means that snapshot artifacts will not be resolved in additional artifact urls.
-            String pattern = getWholePattern().replaceFirst("\\-\\[revision\\]", "-" + rev);
-            ResourcePattern resourcePattern = toResourcePattern(pattern);
-            return findResourceUsingPattern(moduleRevisionId, resourcePattern, artifact, getDefaultRMDParser(artifact.getModuleRevisionId().getModuleId()), date, forDownload);
-        }
         return null;
     }
 
@@ -191,12 +197,6 @@ public class MavenResolver extends ExternalResourceResolver implements PatternBa
         return new MavenMetadata();
     }
 
-    public void dumpSettings() {
-        super.dumpSettings();
-        Message.debug("\t\troot: " + root);
-        Message.debug("\t\tpattern: " + pattern);
-    }
-
     // A bunch of configuration properties that we don't (yet) support in our model via the DSL. Users can still tweak these on the resolver using mavenRepo().
     public boolean isUsepoms() {
         return usepoms;
@@ -254,4 +254,16 @@ public class MavenResolver extends ExternalResourceResolver implements PatternBa
             throw new IllegalArgumentException("Cannot set m2compatible = false on mavenRepo.");
         }
     }
+
+    protected static class TimestampedModuleSource implements ModuleSource {
+        public String getTimestampedVersion() {
+            return timestampedVersion;
+        }
+
+        private final String timestampedVersion;
+
+        public TimestampedModuleSource(String uniqueSnapshotVersion) {
+            this.timestampedVersion = uniqueSnapshotVersion;
+        }
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/RepositoryTransport.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/RepositoryTransport.java
index 156e7d9..910735c 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/RepositoryTransport.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/RepositoryTransport.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.internal.artifacts.repositories.transport;
 
-import org.apache.ivy.plugins.resolver.AbstractResolver;
+import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
 import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository;
 
 import java.net.URI;
@@ -23,7 +23,7 @@ import java.net.URI;
 public interface RepositoryTransport {
     ExternalResourceRepository getRepository();
 
-    void configureCacheManager(AbstractResolver resolver);
+    void configureCacheManager(ExternalResourceResolver resolver);
 
     String convertToPath(URI uri);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/AbstractDependencyResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/AbstractDependencyResult.java
new file mode 100644
index 0000000..4ccb18f
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/AbstractDependencyResult.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.result;
+
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.result.DependencyResult;
+import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
+
+/**
+ * by Szczepan Faber, created at: 7/26/12
+ */
+public class AbstractDependencyResult implements DependencyResult {
+    private final ModuleVersionSelector requested;
+    private final ResolvedModuleVersionResult from;
+
+    public AbstractDependencyResult(ModuleVersionSelector requested, ResolvedModuleVersionResult from) {
+        assert requested != null;
+        assert from != null;
+
+        this.from = from;
+        this.requested = requested;
+    }
+
+    public ModuleVersionSelector getRequested() {
+        return requested;
+    }
+
+    public ResolvedModuleVersionResult getFrom() {
+        return from;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedDependencyResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedDependencyResult.java
index 71e1ed4..074e5e4 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedDependencyResult.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedDependencyResult.java
@@ -23,36 +23,24 @@ import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
 /**
  * by Szczepan Faber, created at: 7/26/12
  */
-public class DefaultResolvedDependencyResult implements ResolvedDependencyResult {
-
-    private final ModuleVersionSelector requested;
+public class DefaultResolvedDependencyResult extends AbstractDependencyResult implements ResolvedDependencyResult {
     private final ResolvedModuleVersionResult selected;
-    private final ResolvedModuleVersionResult from;
 
     public DefaultResolvedDependencyResult(ModuleVersionSelector requested, ResolvedModuleVersionResult selected, ResolvedModuleVersionResult from) {
-        assert requested != null;
-        assert selected != null;
-        assert from != null;
-
-        this.from = from;
-        this.requested = requested;
+        super(requested, from);
         this.selected = selected;
     }
 
-    public ModuleVersionSelector getRequested() {
-        return requested;
-    }
-
     public ResolvedModuleVersionResult getSelected() {
         return selected;
     }
 
-    public ResolvedModuleVersionResult getFrom() {
-        return from;
-    }
-
     @Override
     public String toString() {
-        return ResolvedDependencyResultPrinter.print(this);
+        if (getRequested().matchesStrictly(getSelected().getId())) {
+            return getRequested().toString();
+        } else {
+            return String.format("%s -> %s", getRequested(), getSelected().getId());
+        }
     }
 }
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedModuleVersionResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedModuleVersionResult.java
index e976424..0bff383 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedModuleVersionResult.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedModuleVersionResult.java
@@ -28,8 +28,8 @@ import java.util.LinkedHashSet;
 import java.util.Set;
 
 /**
-* by Szczepan Faber, created at: 8/10/12
-*/
+ * by Szczepan Faber, created at: 8/10/12
+ */
 public class DefaultResolvedModuleVersionResult implements ResolvedModuleVersionResult {
     private final ModuleVersionIdentifier id;
     private final Set<DependencyResult> dependencies = new LinkedHashSet<DependencyResult>();
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultUnresolvedDependencyResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultUnresolvedDependencyResult.java
index e8d8b92..6b95007 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultUnresolvedDependencyResult.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultUnresolvedDependencyResult.java
@@ -17,38 +17,39 @@
 package org.gradle.api.internal.artifacts.result;
 
 import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
 import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
 import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
 
 /**
  * by Szczepan Faber, created at: 7/26/12
  */
-public class DefaultUnresolvedDependencyResult implements UnresolvedDependencyResult {
-
-    private final ModuleVersionSelector requested;
-    private final Exception failure;
-    private final ResolvedModuleVersionResult from;
-
-    public DefaultUnresolvedDependencyResult(ModuleVersionSelector requested, Exception failure, ResolvedModuleVersionResult from) {
-        assert requested != null;
-        assert failure != null;
-        assert from != null;
-
-        this.from = from;
+public class DefaultUnresolvedDependencyResult extends AbstractDependencyResult implements UnresolvedDependencyResult {
+    private final ModuleVersionSelectionReason reason;
+    private final ModuleVersionResolveException failure;
+
+    public DefaultUnresolvedDependencyResult(ModuleVersionSelector requested, ModuleVersionSelectionReason reason,
+                                             ResolvedModuleVersionResult from, ModuleVersionResolveException failure) {
+        super(requested, from);
+        this.reason = reason;
         this.failure = failure;
-        this.requested = requested;
     }
 
-    public ModuleVersionSelector getRequested() {
-        return requested;
+    public ModuleVersionResolveException getFailure() {
+        return failure;
+    }
+
+    public ModuleVersionSelector getAttempted() {
+        return failure.getSelector();
     }
 
-    public ResolvedModuleVersionResult getFrom() {
-        return from;
+    public ModuleVersionSelectionReason getAttemptedReason() {
+        return reason;
     }
 
     @Override
     public String toString() {
-        return requested.getGroup() + ":" + requested.getName() + ":" + requested.getVersion() + " - " + failure.getMessage();
+        return String.format("%s -> %s - %s", getRequested(), getAttempted(), failure.getMessage());
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedArtifact.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedArtifact.java
new file mode 100644
index 0000000..4d3ec1a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedArtifact.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.externalresource.cached;
+
+import java.math.BigInteger;
+
+public interface CachedArtifact extends CachedItem {
+    BigInteger getDescriptorHash();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedArtifactIndex.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedArtifactIndex.java
new file mode 100644
index 0000000..d4b8b6b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedArtifactIndex.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.externalresource.cached;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.externalresource.ivy.ArtifactAtRepositoryKey;
+
+import java.io.File;
+import java.math.BigInteger;
+
+public interface CachedArtifactIndex {
+    /**
+     * Adds a resolution to the index.
+     *
+     * The incoming file is expected to be in the persistent local. This method will not move/copy the file there. <p>
+     *
+     * @param key The key to cache this resolution under in the index. Cannot be null.
+     * @param artifactFile The artifact file in the persistent file store. Cannot be null
+     * @param moduleDescriptorHash The checksum (SHA1) of the related moduledescriptor.
+     * @see #storeMissing(ArtifactAtRepositoryKey, BigInteger)
+     */
+    void store(ArtifactAtRepositoryKey key, File artifactFile, BigInteger moduleDescriptorHash);
+
+    /**
+     * Record that the artifact with the given key was missing.
+     *
+     * @param key The key to cache this resolution under in the index.
+     * @param descriptorHash The SHA1 hash of the related moduleDescriptor
+     */
+    void storeMissing(ArtifactAtRepositoryKey key, BigInteger descriptorHash);
+
+    /**
+     * Lookup a cached resolution.
+     *
+     * The {@link CachedExternalResource#getCachedFile()} is guaranteed to exist at the time that the entry is returned from this method.
+     *
+     * @param key The key to search the index for
+     * @return The cached artifact resolution if one exists, otherwise null.
+     */
+    @Nullable
+    CachedArtifact lookup(ArtifactAtRepositoryKey key);
+
+    /**
+     * Remove the entry for the given key if it exists.
+     *
+     * @param key The key of the item to remove.
+     */
+    void clear(ArtifactAtRepositoryKey key);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResource.java
index 4fc7561..7433766 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResource.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResource.java
@@ -20,40 +20,15 @@ import org.gradle.api.Nullable;
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
 import org.gradle.util.hash.HashValue;
 
-import java.io.File;
 import java.util.Date;
 
 /**
  * A record of some kind of external resource that has been cached locally (typically into the filestore).
  *
- * Note that this is not a kind of {@link org.gradle.api.internal.externalresource.ExternalResource}. There is
- * an adapter that can represent a cached resource as an external resource as {@link CachedExternalResourceAdapter}.
+ * Note that this is not a kind of {@link org.gradle.api.internal.externalresource.ExternalResource}. There is an adapter that can represent a cached resource as an external resource as {@link
+ * CachedExternalResourceAdapter}.
  */
-public interface CachedExternalResource {
-
-    /**
-     * True if this cache entry represents that the resource does not exist.
-     *
-     * For a missing resource, all of the values will be null or similar “non” values.
-     *
-     * @return Whether this is a “missing” entry or not.
-     */
-    boolean isMissing();
-
-    /**
-     * The cached version of the external resource as a local file.
-     *
-     * @return The cached version of the external resource as a local file, or null if this {@link #isMissing()}.
-     */
-    @Nullable
-    File getCachedFile();
-
-    /**
-     * The timestamp of when this cache entry was created.
-     *
-     * @return The timestamp of when this cache entry was created.
-     */
-    long getCachedAt();
+public interface CachedExternalResource extends CachedItem {
 
     /**
      * Always the actual content length of the cached file, not the external source.
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedItem.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedItem.java
new file mode 100644
index 0000000..740c74a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedItem.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.externalresource.cached;
+
+import org.gradle.api.Nullable;
+
+import java.io.File;
+
+public interface CachedItem {
+    /**
+     * True if this cache entry represents that the resource does not exist.
+     *
+     * For a missing resource, all of the values will be null or similar “non” values.
+     *
+     * @return Whether this is a “missing” entry or not.
+     */
+    boolean isMissing();
+
+    /**
+     * The cached version of the external resource as a local file.
+     *
+     * @return The cached version of the external resource as a local file, or null if this {@link #isMissing()}.
+     */
+    @Nullable
+    File getCachedFile();
+
+    /**
+     * The timestamp of when this cache entry was created.
+     *
+     * @return The timestamp of when this cache entry was created.
+     */
+    long getCachedAt();
+
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedArtifact.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedArtifact.java
new file mode 100644
index 0000000..b1da4ff
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedArtifact.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.externalresource.cached;
+
+import java.io.File;
+import java.io.Serializable;
+import java.math.BigInteger;
+
+public class DefaultCachedArtifact implements CachedArtifact, Serializable {
+    private final File cachedFile;
+    private final long cachedAt;
+    private final BigInteger descriptorHash;
+
+    public DefaultCachedArtifact(File cachedFile, long cachedAt, BigInteger descriptorHash) {
+        this.cachedFile = cachedFile;
+        this.cachedAt = cachedAt;
+        this.descriptorHash = descriptorHash;
+    }
+
+    public DefaultCachedArtifact(long cachedAt, BigInteger descriptorHash) {
+        this.cachedAt = cachedAt;
+        this.cachedFile = null;
+        this.descriptorHash = descriptorHash;
+    }
+
+    public boolean isMissing() {
+        return cachedFile == null;
+    }
+
+    public File getCachedFile() {
+        return cachedFile;
+    }
+
+    public long getCachedAt() {
+        return cachedAt;
+    }
+
+    public BigInteger getDescriptorHash() {
+        return descriptorHash;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResourceIndex.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResourceIndex.java
index 25837e4..7f36afe 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResourceIndex.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResourceIndex.java
@@ -17,106 +17,35 @@
 package org.gradle.api.internal.externalresource.cached;
 
 import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.externalresource.ivy.AbstractCachedIndex;
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
-import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.internal.Factory;
 import org.gradle.internal.TimeProvider;
+import org.gradle.messaging.serialize.DefaultSerializer;
 
 import java.io.File;
 import java.io.Serializable;
 
-public class DefaultCachedExternalResourceIndex<K extends Serializable> implements CachedExternalResourceIndex<K> {
+public class DefaultCachedExternalResourceIndex<K extends Serializable> extends AbstractCachedIndex<K, CachedExternalResource> implements CachedExternalResourceIndex<K> {
 
-    private final File persistentCacheFile;
     private final TimeProvider timeProvider;
-    private final CacheLockingManager cacheLockingManager;
-
-    private PersistentIndexedCache<K, DefaultCachedExternalResource> persistentCache;
-    private final Class<K> keyType;
 
     public DefaultCachedExternalResourceIndex(File persistentCacheFile, Class<K> keyType, TimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
-        this.persistentCacheFile = persistentCacheFile;
-        this.keyType = keyType;
+        super(persistentCacheFile, new DefaultSerializer<K>(keyType.getClassLoader()), new DefaultSerializer<CachedExternalResource>(CachedExternalResource.class.getClassLoader()), cacheLockingManager);
         this.timeProvider = timeProvider;
-        this.cacheLockingManager = cacheLockingManager;
-    }
-
-    private PersistentIndexedCache<K, DefaultCachedExternalResource> getPersistentCache() {
-        if (persistentCache == null) {
-            persistentCache = initPersistentCache();
-        }
-        return persistentCache;
-    }
-
-    private PersistentIndexedCache<K, DefaultCachedExternalResource> initPersistentCache() {
-        return cacheLockingManager.createCache(persistentCacheFile, keyType, DefaultCachedExternalResource.class);
-    }
-
-    private DefaultCachedExternalResource createMissingEntry() {
-        return new DefaultCachedExternalResource(timeProvider.getCurrentTime());
     }
 
     private DefaultCachedExternalResource createEntry(File artifactFile, ExternalResourceMetaData externalResourceMetaData) {
         return new DefaultCachedExternalResource(artifactFile, timeProvider.getCurrentTime(), externalResourceMetaData);
     }
 
-    private String operationName(String action) {
-        return String.format("%s artifact resolution cache '%s'", action, persistentCacheFile.getName());
-    }
-
     public void store(final K key, final File artifactFile, ExternalResourceMetaData externalResourceMetaData) {
-        if (artifactFile == null) {
-            throw new IllegalArgumentException("artifactFile cannot be null");
-        }
-        if (key == null) {
-            throw new IllegalArgumentException("key cannot be null");
-        }
+        assertArtifactFileNotNull(artifactFile);
+        assertKeyNotNull(key);
 
         storeInternal(key, createEntry(artifactFile, externalResourceMetaData));
     }
 
     public void storeMissing(K key) {
-        storeInternal(key, createMissingEntry());
+        storeInternal(key, new DefaultCachedExternalResource(timeProvider.getCurrentTime()));
     }
-
-    private void storeInternal(final K key, final DefaultCachedExternalResource entry) {
-        cacheLockingManager.useCache(operationName("store into"), new Runnable() {
-            public void run() {
-                getPersistentCache().put(key, entry);
-            }
-        });
-    }
-
-    public CachedExternalResource lookup(final K key) {
-        if (key == null) {
-            throw new IllegalArgumentException("key cannot be null");
-        }
-
-        return cacheLockingManager.useCache(operationName("lookup from"), new Factory<DefaultCachedExternalResource>() {
-            public DefaultCachedExternalResource create() {
-                DefaultCachedExternalResource found = getPersistentCache().get(key);
-                if (found == null) {
-                    return null;
-                } else if (found.isMissing() || found.getCachedFile().exists()) {
-                    return found;
-                } else {
-                    clear(key);
-                    return null;
-                }
-            }
-        });
-    }
-
-    public void clear(final K key) {
-        if (key == null) {
-            throw new IllegalArgumentException("key cannot be null");
-        }
-
-        cacheLockingManager.useCache(operationName("clear from"), new Runnable() {
-            public void run() {
-                getPersistentCache().remove(key);
-            }
-        });
-    }
-
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/AbstractCachedIndex.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/AbstractCachedIndex.java
new file mode 100644
index 0000000..13bf9ad
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/AbstractCachedIndex.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.externalresource.ivy;
+
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.externalresource.cached.CachedItem;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.internal.Factory;
+import org.gradle.messaging.serialize.Serializer;
+
+import java.io.File;
+
+abstract public class AbstractCachedIndex<K, V extends CachedItem> {
+    private final File persistentCacheFile;
+    private final Serializer<K> keySerializer;
+    private final Serializer<V> valueSerializer;
+    private final CacheLockingManager cacheLockingManager;
+
+    private PersistentIndexedCache<K, V> persistentCache;
+
+    public AbstractCachedIndex(File persistentCacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer, CacheLockingManager cacheLockingManager) {
+
+        this.persistentCacheFile = persistentCacheFile;
+        this.keySerializer = keySerializer;
+        this.valueSerializer = valueSerializer;
+        this.cacheLockingManager = cacheLockingManager;
+    }
+
+    private PersistentIndexedCache<K, V> getPersistentCache() {
+        if (persistentCache == null) {
+            persistentCache = initPersistentCache();
+        }
+        return persistentCache;
+    }
+
+    private PersistentIndexedCache<K, V> initPersistentCache() {
+        return cacheLockingManager.createCache(persistentCacheFile, keySerializer, valueSerializer);
+    }
+
+    private String operationName(String action) {
+        return String.format("%s artifact resolution cache '%s'", action, persistentCacheFile.getName());
+    }
+
+    public V lookup(final K key) {
+        assertKeyNotNull(key);
+
+        return cacheLockingManager.useCache(operationName("lookup from"), new Factory<V>() {
+            public V create() {
+                V found = getPersistentCache().get(key);
+                if (found == null) {
+                    return null;
+                } else if (found.isMissing() || found.getCachedFile().exists()) {
+                    return found;
+                } else {
+                    clear(key);
+                    return null;
+                }
+            }
+        });
+    }
+
+    protected void storeInternal(final K key, final V entry) {
+        cacheLockingManager.useCache(operationName("store into"), new Runnable() {
+            public void run() {
+                getPersistentCache().put(key, entry);
+            }
+        });
+    }
+
+    protected void assertKeyNotNull(K key) {
+        if (key == null) {
+            throw new IllegalArgumentException("key cannot be null");
+        }
+    }
+
+    protected void assertArtifactFileNotNull(File artifactFile) {
+        if (artifactFile == null) {
+            throw new IllegalArgumentException("artifactFile cannot be null");
+        }
+    }
+
+    public void clear(final K key) {
+        assertKeyNotNull(key);
+        cacheLockingManager.useCache(operationName("clear from"), new Runnable() {
+            public void run() {
+                getPersistentCache().remove(key);
+            }
+        });
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedArtifactIndex.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedArtifactIndex.java
new file mode 100644
index 0000000..9f297a4
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedArtifactIndex.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.externalresource.ivy;
+
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.externalresource.cached.CachedArtifact;
+import org.gradle.api.internal.externalresource.cached.CachedArtifactIndex;
+import org.gradle.api.internal.externalresource.cached.DefaultCachedArtifact;
+import org.gradle.internal.TimeProvider;
+import org.gradle.messaging.serialize.DataStreamBackedSerializer;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.File;
+import java.io.IOException;
+import java.math.BigInteger;
+
+public class ArtifactAtRepositoryCachedArtifactIndex extends AbstractCachedIndex<ArtifactAtRepositoryKey, CachedArtifact> implements CachedArtifactIndex {
+    private final TimeProvider timeProvider;
+
+    public ArtifactAtRepositoryCachedArtifactIndex(File persistentCacheFile, TimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
+        super(persistentCacheFile, new ArtifactAtRepositoryKeySerializer(), new CachedArtifactSerializer(), cacheLockingManager);
+        this.timeProvider = timeProvider;
+    }
+
+    private DefaultCachedArtifact createEntry(File artifactFile, BigInteger moduleDescriptorHash) {
+        return new DefaultCachedArtifact(artifactFile, timeProvider.getCurrentTime(), moduleDescriptorHash);
+    }
+
+    public void store(final ArtifactAtRepositoryKey key, final File artifactFile, BigInteger moduleDescriptorHash) {
+        assertArtifactFileNotNull(artifactFile);
+        assertKeyNotNull(key);
+        storeInternal(key, createEntry(artifactFile, moduleDescriptorHash));
+    }
+
+    public void storeMissing(ArtifactAtRepositoryKey key, BigInteger descriptorHash) {
+        storeInternal(key, createMissingEntry(descriptorHash));
+    }
+
+    CachedArtifact createMissingEntry(BigInteger descriptorHash) {
+        return new DefaultCachedArtifact(timeProvider.getCurrentTime(), descriptorHash);
+    }
+
+    private static class ArtifactAtRepositoryKeySerializer extends DataStreamBackedSerializer<ArtifactAtRepositoryKey> {
+        @Override
+        public void write(DataOutput dataOutput, ArtifactAtRepositoryKey value) throws IOException {
+            dataOutput.writeUTF(value.getRepositoryId());
+            dataOutput.writeUTF(value.getArtifactId());
+        }
+
+        @Override
+        public ArtifactAtRepositoryKey read(DataInput dataInput) throws IOException {
+            String repositoryId = dataInput.readUTF();
+            String artifactId = dataInput.readUTF();
+            return new ArtifactAtRepositoryKey(repositoryId, artifactId);
+        }
+    }
+
+    private static class CachedArtifactSerializer extends DataStreamBackedSerializer<CachedArtifact> {
+        @Override
+        public void write(DataOutput dataOutput, CachedArtifact value) throws IOException {
+            dataOutput.writeBoolean(value.isMissing());
+            if (!value.isMissing()) {
+                dataOutput.writeUTF(value.getCachedFile().getPath());
+            }
+            dataOutput.writeLong(value.getCachedAt());
+            byte[] hash = value.getDescriptorHash().toByteArray();
+            dataOutput.writeInt(hash.length);
+            dataOutput.write(hash);
+        }
+
+        @Override
+        public CachedArtifact read(DataInput dataInput) throws Exception {
+            boolean isMissing = dataInput.readBoolean();
+            File file;
+            if (!isMissing) {
+                file = new File(dataInput.readUTF());
+            } else {
+                file = null;
+            }
+            long createTimestamp = dataInput.readLong();
+            int count = dataInput.readInt();
+            byte[] encodedHash = new byte[count];
+            dataInput.readFully(encodedHash);
+            BigInteger hash = new BigInteger(encodedHash);
+            return new DefaultCachedArtifact(file, createTimestamp, hash);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedExternalResourceIndex.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedExternalResourceIndex.java
deleted file mode 100644
index 0fd2ea8..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedExternalResourceIndex.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.externalresource.ivy;
-
-import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
-import org.gradle.api.internal.externalresource.cached.DefaultCachedExternalResourceIndex;
-import org.gradle.internal.TimeProvider;
-
-import java.io.File;
-
-public class ArtifactAtRepositoryCachedExternalResourceIndex extends DefaultCachedExternalResourceIndex<ArtifactAtRepositoryKey> {
-
-    public ArtifactAtRepositoryCachedExternalResourceIndex(File persistentCacheFile, TimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
-        super(persistentCacheFile, ArtifactAtRepositoryKey.class, timeProvider, cacheLockingManager);
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryKey.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryKey.java
index 41d9760..760159f 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryKey.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryKey.java
@@ -22,18 +22,21 @@ import org.apache.ivy.core.module.descriptor.DefaultArtifact;
 import org.apache.ivy.core.module.id.ArtifactRevisionId;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
 
-import java.io.Serializable;
-
-public class ArtifactAtRepositoryKey implements Serializable {
-    private final String resolverId;
+public class ArtifactAtRepositoryKey {
+    private final String repositoryId;
     private final String artifactId;
 
     public ArtifactAtRepositoryKey(ModuleVersionRepository repository, ArtifactRevisionId artifactId) {
         this(repository, getArtifactKey(artifactId));
     }
 
+    public ArtifactAtRepositoryKey(String repositoryId, String artifactId) {
+        this.repositoryId = repositoryId;
+        this.artifactId = artifactId;
+    }
+
     private ArtifactAtRepositoryKey(ModuleVersionRepository repository, String artifactPath) {
-        this.resolverId = repository.getId();
+        this.repositoryId = repository.getId();
         this.artifactId = artifactPath;
     }
 
@@ -43,9 +46,17 @@ public class ArtifactAtRepositoryKey implements Serializable {
         return IvyPatternHelper.substitute(format, dummyArtifact);
     }
 
+    public String getArtifactId() {
+        return artifactId;
+    }
+
+    public String getRepositoryId() {
+        return repositoryId;
+    }
+
     @Override
     public String toString() {
-        return resolverId + ":" + artifactId;
+        return repositoryId + ":" + artifactId;
     }
 
     @Override
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocallyAvailableResourceFinderFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocallyAvailableResourceFinderFactory.java
index 669d420..3cec598 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocallyAvailableResourceFinderFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocallyAvailableResourceFinderFactory.java
@@ -19,6 +19,9 @@ import org.apache.ivy.core.module.id.ArtifactRevisionId;
 import org.gradle.api.internal.artifacts.ivyservice.ArtifactCacheMetaData;
 import org.gradle.api.internal.artifacts.mvnsettings.CannotLocateLocalMavenRepositoryException;
 import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator;
+import org.gradle.api.internal.artifacts.repositories.resolver.IvyResourcePattern;
+import org.gradle.api.internal.artifacts.repositories.resolver.M2ResourcePattern;
+import org.gradle.api.internal.artifacts.repositories.resolver.ResourcePattern;
 import org.gradle.api.internal.externalresource.local.*;
 import org.gradle.api.internal.filestore.FileStoreSearcher;
 import org.gradle.internal.Factory;
@@ -52,6 +55,9 @@ public class LocallyAvailableResourceFinderFactory implements Factory<LocallyAva
         // The current filestore
         finders.add(new LocallyAvailableResourceFinderSearchableFileStoreAdapter<ArtifactRevisionId>(fileStore));
 
+        // 1.3
+        addForPattern(finders, "artifacts-15", "filestore/[organisation]/[module](/[branch])/[revision]/[type]/*/[artifact]-[revision](-[classifier])(.[ext])");
+
         // 1.1, 1.2
         addForPattern(finders, "artifacts-14", "filestore/[organisation]/[module](/[branch])/[revision]/[type]/*/[artifact]-[revision](-[classifier])(.[ext])");
 
@@ -71,12 +77,11 @@ public class LocallyAvailableResourceFinderFactory implements Factory<LocallyAva
         // Milestone 3
         addForPattern(finders, "../cache", "[organisation]/[module](/[branch])/[type]s/[artifact]-[revision](-[classifier])(.[ext])");
 
-
         // Maven local
         try {
             File localMavenRepository = localMavenRepositoryLocator.getLocalMavenRepository();
             if (localMavenRepository.exists()) {
-                addForPattern(finders, localMavenRepository, "[organisation-path]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])");
+                addForPattern(finders, localMavenRepository, new M2ResourcePattern("[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])"));
             }
         } catch (CannotLocateLocalMavenRepositoryException ex) {
             finders.add(new NoMavenLocalRepositoryResourceFinder(ex));
@@ -85,10 +90,10 @@ public class LocallyAvailableResourceFinderFactory implements Factory<LocallyAva
     }
 
     private void addForPattern(List<LocallyAvailableResourceFinder<ArtifactRevisionId>> finders, String path, String pattern) {
-        addForPattern(finders, new File(rootCachesDirectory, path), pattern);
+        addForPattern(finders, new File(rootCachesDirectory, path), new IvyResourcePattern(pattern));
     }
 
-    private void addForPattern(List<LocallyAvailableResourceFinder<ArtifactRevisionId>> finders, File baseDir, String pattern) {
+    private void addForPattern(List<LocallyAvailableResourceFinder<ArtifactRevisionId>> finders, File baseDir, ResourcePattern pattern) {
         if (baseDir.exists()) {
             finders.add(new PatternBasedLocallyAvailableResourceFinder(baseDir, pattern));
         }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/PatternBasedLocallyAvailableResourceFinder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/PatternBasedLocallyAvailableResourceFinder.java
index c19f639..aeea5a0 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/PatternBasedLocallyAvailableResourceFinder.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/PatternBasedLocallyAvailableResourceFinder.java
@@ -15,17 +15,16 @@
  */
 package org.gradle.api.internal.externalresource.local.ivy;
 
-import org.apache.ivy.core.IvyPatternHelper;
 import org.apache.ivy.core.module.descriptor.Artifact;
 import org.apache.ivy.core.module.descriptor.DefaultArtifact;
 import org.apache.ivy.core.module.id.ArtifactRevisionId;
 import org.gradle.api.Transformer;
 import org.gradle.api.file.EmptyFileVisitor;
 import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.internal.artifacts.repositories.resolver.ResourcePattern;
 import org.gradle.api.internal.externalresource.local.AbstractLocallyAvailableResourceFinder;
-import org.gradle.api.internal.file.collections.DirectoryFileTree;
-import org.gradle.api.tasks.util.PatternFilterable;
-import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.api.internal.file.collections.MinimalFileTree;
+import org.gradle.api.internal.file.collections.SingleIncludePatternFileTree;
 import org.gradle.internal.Factory;
 
 import java.io.File;
@@ -34,32 +33,12 @@ import java.util.List;
 
 public class PatternBasedLocallyAvailableResourceFinder extends AbstractLocallyAvailableResourceFinder<ArtifactRevisionId> {
 
-    public PatternBasedLocallyAvailableResourceFinder(File baseDir, String pattern) {
+    public PatternBasedLocallyAvailableResourceFinder(File baseDir, ResourcePattern pattern) {
         super(createProducer(baseDir, pattern));
     }
 
-    private static Transformer<Factory<List<File>>, ArtifactRevisionId> createProducer(final File baseDir, final String pattern) {
+    private static Transformer<Factory<List<File>>, ArtifactRevisionId> createProducer(final File baseDir, final ResourcePattern pattern) {
         return new Transformer<Factory<List<File>>, ArtifactRevisionId>() {
-            DirectoryFileTree fileTree = new DirectoryFileTree(baseDir);
-
-            private String getArtifactPattern(ArtifactRevisionId artifactId) {
-                String substitute = pattern;
-                // Need to handle organisation values that have been munged for m2compatible
-                substitute = IvyPatternHelper.substituteToken(substitute, "organisation", artifactId.getModuleRevisionId().getOrganisation().replace('/', '.'));
-                substitute = IvyPatternHelper.substituteToken(substitute, "organisation-path", artifactId.getModuleRevisionId().getOrganisation().replace('.', '/'));
-
-                Artifact dummyArtifact = new DefaultArtifact(artifactId, null, null, false);
-                substitute = IvyPatternHelper.substitute(substitute, dummyArtifact);
-                return substitute;
-            }
-
-            private DirectoryFileTree getMatchingFiles(ArtifactRevisionId artifact) {
-                String patternString = getArtifactPattern(artifact);
-                PatternFilterable pattern = new PatternSet();
-                pattern.include(patternString);
-                return fileTree.filter(pattern);
-            }
-
             public Factory<List<File>> transform(final ArtifactRevisionId artifactId) {
                 return new Factory<List<File>>() {
                     public List<File> create() {
@@ -75,6 +54,16 @@ public class PatternBasedLocallyAvailableResourceFinder extends AbstractLocallyA
                     }
                 };
             }
+
+            private MinimalFileTree getMatchingFiles(ArtifactRevisionId artifact) {
+                String patternString = getArtifactPattern(artifact);
+                return new SingleIncludePatternFileTree(baseDir, patternString);
+            }
+
+            private String getArtifactPattern(ArtifactRevisionId artifactId) {
+                Artifact dummyArtifact = new DefaultArtifact(artifactId, null, null, false);
+                return pattern.toPath(dummyArtifact);
+            }
         };
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileResourceConnector.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileResourceConnector.java
index b6ab96b..c375699 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileResourceConnector.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileResourceConnector.java
@@ -24,11 +24,15 @@ import org.gradle.api.internal.externalresource.transfer.ExternalResourceAccesso
 import org.gradle.api.internal.externalresource.transfer.ExternalResourceLister;
 import org.gradle.api.internal.externalresource.transfer.ExternalResourceUploader;
 import org.gradle.internal.Factory;
+import org.gradle.util.GFileUtils;
 import org.gradle.util.hash.HashValue;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.*;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -56,7 +60,7 @@ public class FileResourceConnector implements ExternalResourceLister, ExternalRe
         if (!target.canWrite()) {
             target.delete();
         } // if target is writable, the copy will overwrite it without requiring a delete
-        target.getParentFile().mkdirs();
+        GFileUtils.mkdirs(target.getParentFile());
         FileOutputStream fileOutputStream = new FileOutputStream(target);
         try {
             InputStream sourceInputStream = source.create();
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileTransport.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileTransport.java
index 7090de6..652e5d6 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileTransport.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileTransport.java
@@ -16,8 +16,8 @@
 package org.gradle.api.internal.externalresource.transport.file;
 
 import org.apache.ivy.core.cache.RepositoryCacheManager;
-import org.apache.ivy.plugins.resolver.AbstractResolver;
 import org.gradle.api.Nullable;
+import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
 import org.gradle.api.internal.externalresource.ExternalResource;
 import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceCandidates;
@@ -33,20 +33,24 @@ import java.net.URI;
 public class FileTransport implements RepositoryTransport {
     private final String name;
     private final RepositoryCacheManager repositoryCacheManager;
-    private final TemporaryFileProvider temporaryFileProvider;
+    private final ExternalResourceRepository repository;
 
     public FileTransport(String name, RepositoryCacheManager repositoryCacheManager, TemporaryFileProvider temporaryFileProvider) {
         this.name = name;
         this.repositoryCacheManager = repositoryCacheManager;
-        this.temporaryFileProvider = temporaryFileProvider;
+        repository = createRepository(temporaryFileProvider);
     }
 
     public ExternalResourceRepository getRepository() {
+        return repository;
+    }
+
+    public ExternalResourceRepository createRepository(TemporaryFileProvider temporaryFileProvider) {
         FileResourceConnector connector = new FileResourceConnector();
         return new DefaultExternalResourceRepository(name, connector, connector, connector, temporaryFileProvider, new NoOpCacheAwareExternalResourceAccessor(connector));
     }
 
-    public void configureCacheManager(AbstractResolver resolver) {
+    public void configureCacheManager(ExternalResourceResolver resolver) {
         resolver.setRepositoryCacheManager(repositoryCacheManager);
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientHelper.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientHelper.java
index 6075676..b482f77 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientHelper.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientHelper.java
@@ -40,11 +40,20 @@ public class HttpClientHelper {
     private final BasicHttpContext httpContext = new BasicHttpContext();
 
     public HttpClientHelper(HttpSettings settings) {
+        alwaysUseKeepAliveConnections();
+
         DefaultHttpClient client = new SystemDefaultHttpClient();
         new HttpClientConfigurer(settings).configure(client);
         this.client = new DecompressingHttpClient(client);
     }
 
+    private void alwaysUseKeepAliveConnections() {
+        // HttpClient 4.2.2 does not use the correct default value for "http.keepAlive" system property (default is "true").
+        // HttpClient NTLM authentication fails badly when this property value is true.
+        // So we force it to be true here: effectively, we're ignoring any user-supplied value for our HttpClient configuration.
+        System.setProperty("http.keepAlive", "true");
+    }
+
     public HttpResponse performRawHead(String source) {
         return performRequest(new HttpHead(source));        
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpTransport.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpTransport.java
index 26f7876..c23ea02 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpTransport.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpTransport.java
@@ -16,15 +16,15 @@
 package org.gradle.api.internal.externalresource.transport.http;
 
 import org.apache.ivy.core.cache.RepositoryCacheManager;
-import org.apache.ivy.plugins.resolver.AbstractResolver;
 import org.gradle.api.artifacts.repositories.PasswordCredentials;
+import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
 import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
 import org.gradle.api.internal.externalresource.transfer.DefaultCacheAwareExternalResourceAccessor;
-import org.gradle.api.internal.externalresource.transport.DefaultExternalResourceRepository;
-import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository;
-import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
 import org.gradle.api.internal.externalresource.transfer.ProgressLoggingExternalResourceAccessor;
 import org.gradle.api.internal.externalresource.transfer.ProgressLoggingExternalResourceUploader;
+import org.gradle.api.internal.externalresource.transport.DefaultExternalResourceRepository;
+import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository;
 import org.gradle.api.internal.file.TemporaryFileProvider;
 import org.gradle.logging.ProgressLoggerFactory;
 
@@ -32,24 +32,23 @@ import java.net.URI;
 
 public class HttpTransport implements RepositoryTransport {
     private final String name;
-    private final PasswordCredentials credentials;
     private final RepositoryCacheManager repositoryCacheManager;
-    private final ProgressLoggerFactory progressLoggerFactory;
-    private final TemporaryFileProvider temporaryFileProvider;
-    private final CachedExternalResourceIndex<String> cachedExternalResourceIndex;
+    private final ExternalResourceRepository repository;
 
     public HttpTransport(String name, PasswordCredentials credentials, RepositoryCacheManager repositoryCacheManager,
                          ProgressLoggerFactory progressLoggerFactory, TemporaryFileProvider temporaryFileProvider,
                          CachedExternalResourceIndex<String> cachedExternalResourceIndex) {
         this.name = name;
-        this.credentials = credentials;
         this.repositoryCacheManager = repositoryCacheManager;
-        this.progressLoggerFactory = progressLoggerFactory;
-        this.temporaryFileProvider = temporaryFileProvider;
-        this.cachedExternalResourceIndex = cachedExternalResourceIndex;
+        repository = createRepository(credentials, progressLoggerFactory, temporaryFileProvider, cachedExternalResourceIndex);
     }
 
     public ExternalResourceRepository getRepository() {
+        return repository;
+    }
+
+    private ExternalResourceRepository createRepository(PasswordCredentials credentials, ProgressLoggerFactory progressLoggerFactory,
+                                                        TemporaryFileProvider temporaryFileProvider, CachedExternalResourceIndex<String> cachedExternalResourceIndex) {
         HttpClientHelper http = new HttpClientHelper(new DefaultHttpSettings(credentials));
         HttpResourceAccessor accessor = new HttpResourceAccessor(http);
         HttpResourceUploader uploader = new HttpResourceUploader(http);
@@ -64,7 +63,7 @@ public class HttpTransport implements RepositoryTransport {
         );
     }
 
-    public void configureCacheManager(AbstractResolver resolver) {
+    public void configureCacheManager(ExternalResourceResolver resolver) {
         resolver.setRepositoryCacheManager(repositoryCacheManager);
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyStringNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyStringNotationParser.java
index cb258ce..45f2f5e 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyStringNotationParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyStringNotationParser.java
@@ -19,10 +19,10 @@ package org.gradle.api.internal.notations;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.ClientModule;
 import org.gradle.api.artifacts.ExternalDependency;
-import org.gradle.internal.reflect.Instantiator;
+import org.gradle.api.internal.artifacts.dsl.ParsedModuleStringNotation;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ModuleFactoryHelper;
-import org.gradle.api.internal.artifacts.dsl.dependencies.ParsedModuleStringNotation;
 import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+import org.gradle.internal.reflect.Instantiator;
 
 import java.util.Collection;
 import java.util.regex.Matcher;
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServicesTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServicesTest.groovy
index f102ef0..82361c4 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServicesTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServicesTest.groovy
@@ -18,25 +18,26 @@ package org.gradle.api.internal.artifacts
 import org.gradle.StartParameter
 import org.gradle.api.internal.ClassPathRegistry
 import org.gradle.api.internal.DomainObjectContext
-import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal
 import org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider
 import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder
+import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.DefaultResolutionStrategy
 import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.file.TemporaryFileProvider
 import org.gradle.cache.CacheRepository
 import org.gradle.cache.DirectoryCacheBuilder
 import org.gradle.cache.PersistentCache
 import org.gradle.cache.internal.FileLockManager
 import org.gradle.internal.Factory
+import org.gradle.internal.TimeProvider
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.internal.service.ServiceRegistry
 import org.gradle.listener.ListenerManager
 import org.gradle.logging.LoggingManagerInternal
 import org.gradle.logging.ProgressLoggerFactory
-import org.gradle.internal.TimeProvider
 import spock.lang.Specification
-import org.gradle.api.internal.file.TemporaryFileProvider
 
 class DefaultDependencyManagementServicesTest extends Specification {
     final ServiceRegistry parent = Mock()
@@ -81,47 +82,44 @@ class DefaultDependencyManagementServicesTest extends Specification {
 
     def "can create dependency resolution services"() {
         given:
-        _ * parent.get(Instantiator.class) >> instantiator
-        _ * parent.get(StartParameter.class) >> startParameter
-        1 * instantiator.newInstance(DefaultRepositoryHandler.class, _, _) >> repositoryHandler
-        1 * instantiator.newInstance(DefaultConfigurationContainer.class, !null, instantiator,
+        _ * parent.get(Instantiator) >> instantiator
+        _ * parent.get(StartParameter) >> startParameter
+        1 * instantiator.newInstance(DefaultRepositoryHandler, _, _) >> repositoryHandler
+        1 * instantiator.newInstance(DefaultConfigurationContainer, !null, instantiator,
                 domainObjectContext, listenerManager, dependencyMetaDataProvider) >> configurationContainer
+        def strategy = new DefaultResolutionStrategy()
+        instantiator.newInstance(DefaultResolutionStrategy) >> strategy
 
         when:
         def resolutionServices = services.create(fileResolver, dependencyMetaDataProvider, projectFinder, domainObjectContext)
 
         then:
-        resolutionServices.resolveRepositoryHandler != null
-        resolutionServices.configurationContainer != null
-        resolutionServices.dependencyHandler != null
-        resolutionServices.artifactHandler != null
-        resolutionServices.publishServicesFactory != null
+        resolutionServices.resolveRepositoryHandler
+        resolutionServices.configurationContainer
+        resolutionServices.dependencyHandler
+        resolutionServices.artifactHandler
+        resolutionServices.createArtifactPublicationServices()
     }
 
     def "publish services provide a repository handler"() {
         DefaultRepositoryHandler publishRepositoryHandler = Mock()
 
         given:
-        _ * parent.get(StartParameter.class) >> startParameter
-        _ * parent.get(Instantiator.class) >> instantiator
-        _ * instantiator.newInstance(DefaultRepositoryHandler.class, _, _) >> publishRepositoryHandler
+        _ * parent.get(Instantiator) >> instantiator
+        _ * instantiator.newInstance(DefaultRepositoryHandler, _, _) >> publishRepositoryHandler
 
         when:
         def resolutionServices = services.create(fileResolver, dependencyMetaDataProvider, projectFinder, domainObjectContext)
-        def publishResolverHandler = resolutionServices.publishServicesFactory.create().repositoryHandler
+        def publishResolverHandler = resolutionServices.createArtifactPublicationServices().createRepositoryHandler()
 
         then:
         publishResolverHandler == publishRepositoryHandler
     }
 
     def "publish services provide an ArtifactPublisher"() {
-        given:
-        _ * parent.get(StartParameter.class) >> startParameter
-        _ * parent.get(Instantiator.class) >> instantiator
-
         when:
         def resolutionServices = services.create(fileResolver, dependencyMetaDataProvider, projectFinder, domainObjectContext)
-        def ivyService = resolutionServices.publishServicesFactory.create().artifactPublisher
+        def ivyService = resolutionServices.createArtifactPublicationServices().createArtifactPublisher()
 
         then:
         ivyService != null
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleIdentifierSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleIdentifierSpec.groovy
new file mode 100755
index 0000000..38ed2b5
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleIdentifierSpec.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts
+
+import spock.lang.Specification
+
+import static org.gradle.util.Matchers.strictlyEqual
+
+class DefaultModuleIdentifierSpec extends Specification {
+    
+    def "has useful toString()"() {
+        def module = new DefaultModuleIdentifier("org.foo", "bar")
+
+        expect:
+        module.toString().contains("org.foo:bar")
+    }
+
+    def "ids are equal when group, module and version are equal"() {
+        def module = new DefaultModuleIdentifier("group", "module")
+        def same = new DefaultModuleIdentifier("group", "module")
+        def differentGroup = new DefaultModuleIdentifier("other", "module")
+        def differentModule = new DefaultModuleIdentifier("group", "other")
+
+        expect:
+        module strictlyEqual(same)
+        module != differentGroup
+        module != differentModule
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifierSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifierSpec.groovy
new file mode 100755
index 0000000..ed70946
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifierSpec.groovy
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts
+
+import spock.lang.Specification
+
+import static org.gradle.util.Matchers.strictlyEqual
+
+class DefaultModuleVersionIdentifierSpec extends Specification {
+    def "has useful toString()"() {
+        def module = new DefaultModuleVersionIdentifier("group", "module", "version")
+
+        expect:
+        module.toString().contains("group:module:version")
+    }
+
+    def "ids are equal when group, module and version are equal"() {
+        def module = new DefaultModuleVersionIdentifier("group", "module", "version")
+        def same = new DefaultModuleVersionIdentifier("group", "module", "version")
+        def differentGroup = new DefaultModuleVersionIdentifier("other", "module", "version")
+        def differentModule = new DefaultModuleVersionIdentifier("group", "other", "version")
+        def differentVersion = new DefaultModuleVersionIdentifier("group", "module", "other")
+
+        expect:
+        module strictlyEqual(same)
+        module != differentGroup
+        module != differentModule
+        module != differentVersion
+    }
+
+    def "provides module identifier"() {
+        expect:
+        def id = new DefaultModuleVersionIdentifier("org.gradle", "tooling-api", "1.3")
+        id.group == id.module.group
+        id.name == id.module.name
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierSpec.groovy
new file mode 100644
index 0000000..e975224
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierSpec.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.api.internal.artifacts
+
+import spock.lang.Specification
+import org.gradle.util.Matchers
+
+class ResolvedConfigurationIdentifierSpec extends Specification {
+    def equalsAndHashCode() {
+        when:
+        ResolvedConfigurationIdentifier id = new ResolvedConfigurationIdentifier('group', 'name', 'version', 'config')
+        ResolvedConfigurationIdentifier same = new ResolvedConfigurationIdentifier('group', 'name', 'version', 'config')
+        ResolvedConfigurationIdentifier differentGroup = new ResolvedConfigurationIdentifier('other', 'name', 'version', 'config')
+        ResolvedConfigurationIdentifier differentName = new ResolvedConfigurationIdentifier('group', 'other', 'version', 'config')
+        ResolvedConfigurationIdentifier differentVersion = new ResolvedConfigurationIdentifier('group', 'name', 'other', 'config')
+        ResolvedConfigurationIdentifier differentConfig = new ResolvedConfigurationIdentifier('group', 'name', 'version', 'other')
+
+        then:
+        Matchers.strictlyEquals(id, same)
+        id != differentGroup
+        id != differentName
+        id != differentVersion
+        id != differentConfig
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ResolverResultsSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ResolverResultsSpec.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ResolverResultsSpec.groovy
rename to subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ResolverResultsSpec.groovy
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsTest.java
new file mode 100644
index 0000000..5d97ed4
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.configurations;
+
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Hans Dockter
+ */
+public class ConfigurationsTest {
+    private static final String TEST_CONF = "testConf";
+
+    @Test
+    public void testUploadInternalTaskName() {
+        assertThat(Configurations.uploadInternalTaskName(TEST_CONF), Matchers.equalTo("uploadTestConfInternal"));
+    }
+
+    @Test
+    public void testUploadTaskName() {
+        assertThat(Configurations.uploadTaskName(TEST_CONF), Matchers.equalTo("uploadTestConf"));
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy
new file mode 100644
index 0000000..22e889c
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.configurations;
+
+
+import org.gradle.api.artifacts.UnknownConfigurationException
+import org.gradle.api.internal.DomainObjectContext
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
+import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.DefaultResolutionStrategy
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.listener.ListenerManager
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+public class DefaultConfigurationContainerSpec extends Specification {
+
+    private ArtifactDependencyResolver dependencyResolver = Mock()
+    private Instantiator instantiator = Mock()
+    private DomainObjectContext domainObjectContext = Mock()
+    private ListenerManager listenerManager = Mock()
+    private DependencyMetaDataProvider metaDataProvider = Mock()
+
+    def ConfigurationInternal conf = Mock()
+
+    private DefaultConfigurationContainer configurationContainer = new DefaultConfigurationContainer(
+            dependencyResolver, instantiator, domainObjectContext,
+            listenerManager, metaDataProvider);
+
+    def "adds and gets"() {
+        _ * conf.getName() >> "compile"
+        1 * domainObjectContext.absoluteProjectPath("compile") >> ":compile"
+        1 * instantiator.newInstance(DefaultResolutionStrategy.class) >> { new DefaultResolutionStrategy() }
+        1 * instantiator.newInstance(DefaultConfiguration.class, ":compile", "compile", configurationContainer,
+                dependencyResolver, listenerManager, metaDataProvider, _ as ResolutionStrategyInternal) >> conf
+
+        when:
+        def compile = configurationContainer.add("compile")
+
+        then:
+        configurationContainer.getByName("compile") == compile
+
+        when:
+        configurationContainer.getByName("fooo")
+
+        then:
+        thrown(UnknownConfigurationException)
+    }
+
+    def "configures and finds"() {
+        _ * conf.getName() >> "compile"
+        1 * domainObjectContext.absoluteProjectPath("compile") >> ":compile"
+        1 * instantiator.newInstance(DefaultResolutionStrategy.class) >> { new DefaultResolutionStrategy() }
+        1 * instantiator.newInstance(DefaultConfiguration.class, ":compile", "compile", configurationContainer,
+                dependencyResolver, listenerManager, metaDataProvider, _ as ResolutionStrategyInternal) >> conf
+
+        when:
+        def compile = configurationContainer.add("compile") {
+            description = "I compile!"
+        }
+
+        then:
+        configurationContainer.getByName("compile") == compile
+        1 * conf.setDescription("I compile!")
+
+        //finds configurations
+        configurationContainer.findByName("compile") == compile
+        configurationContainer.findByName("fooo") == null
+        configurationContainer.findAll { it.name == "compile" } as Set == [compile] as Set
+        configurationContainer.findAll { it.name == "fooo" } as Set == [] as Set
+
+        configurationContainer as List == [compile] as List
+    }
+
+    def "creates detached"() {
+        given:
+        def dependency1 = HelperUtil.createDependency("group1", "name1", "version1");
+        def dependency2 = HelperUtil.createDependency("group2", "name2", "version2");
+
+        when:
+        def detached = configurationContainer.detachedConfiguration(dependency1, dependency2);
+
+        then:
+        detached.getAll() == [detached] as Set
+        detached.getHierarchy() == [detached] as Set
+        [dependency1, dependency2].each { detached.getDependencies().contains(it) }
+        detached.getDependencies().size() == 2
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy
new file mode 100644
index 0000000..ad10616
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.configurations
+
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.UnknownConfigurationException
+import org.gradle.api.internal.AsmBackedClassGenerator
+import org.gradle.api.internal.ClassGeneratorBackedInstantiator
+import org.gradle.api.internal.DomainObjectContext
+import org.gradle.api.internal.MissingMethodException
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.listener.ListenerManager
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.assertThat
+
+/**
+ * @author Hans Dockter
+ */
+
+ at RunWith(JMock)
+class DefaultConfigurationContainerTest {
+    private JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+
+    private ArtifactDependencyResolver dependencyResolver = context.mock(ArtifactDependencyResolver)
+    private ListenerManager listenerManager = context.mock(ListenerManager.class)
+    private DependencyMetaDataProvider metaDataProvider = context.mock(DependencyMetaDataProvider.class)
+    private Instantiator instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator())
+    private DefaultConfigurationContainer configurationHandler = instantiator.newInstance(DefaultConfigurationContainer.class,
+            dependencyResolver, instantiator, { name -> name } as DomainObjectContext,
+            listenerManager, metaDataProvider)
+
+    @Before
+    public void setup() {
+        context.checking {
+            ignoring(listenerManager)
+        }
+    }
+
+    @Test
+    void addsNewConfigurationWhenConfiguringSelf() {
+        configurationHandler.configure {
+            newConf
+        }
+        assertThat(configurationHandler.findByName('newConf'), notNullValue())
+        assertThat(configurationHandler.newConf, notNullValue())
+    }
+
+    @Test(expected = UnknownConfigurationException)
+    void doesNotAddNewConfigurationWhenNotConfiguringSelf() {
+        configurationHandler.getByName('unknown')
+    }
+
+    @Test
+    void makesExistingConfigurationAvailableAsProperty() {
+        Configuration configuration = configurationHandler.add('newConf')
+        assertThat(configuration, notNullValue())
+        assertThat(configurationHandler.getByName("newConf"), sameInstance(configuration))
+        assertThat(configurationHandler.newConf, sameInstance(configuration))
+    }
+
+    @Test
+    void addsNewConfigurationWithClosureWhenConfiguringSelf() {
+        String someDesc = 'desc1'
+        configurationHandler.configure {
+            newConf {
+                description = someDesc
+            }
+        }
+        assertThat(configurationHandler.newConf.getDescription(), equalTo(someDesc))
+    }
+
+    @Test
+    void makesExistingConfigurationAvailableAsConfigureMethod() {
+        String someDesc = 'desc1'
+        configurationHandler.add('newConf')
+        Configuration configuration = configurationHandler.newConf {
+            description = someDesc
+        }
+        assertThat(configuration.getDescription(), equalTo(someDesc))
+    }
+
+    @Test
+    void makesExistingConfigurationAvailableAsConfigureMethodWhenConfiguringSelf() {
+        String someDesc = 'desc1'
+        Configuration configuration = configurationHandler.add('newConf')
+        configurationHandler.configure {
+            newConf {
+                description = someDesc
+            }
+        }
+        assertThat(configuration.getDescription(), equalTo(someDesc))
+    }
+
+    @Test(expected = MissingMethodException)
+    void newConfigurationWithNonClosureParametersShouldThrowMissingMethodEx() {
+        configurationHandler.newConf('a', 'b')
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy
new file mode 100644
index 0000000..e71010d
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.configurations
+
+import org.gradle.api.Action
+import org.gradle.api.Task
+import org.gradle.api.artifacts.result.ResolutionResult
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
+import org.gradle.api.internal.artifacts.ResolverResults
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
+import org.gradle.api.tasks.TaskDependency
+import org.gradle.listener.ListenerBroadcast
+import org.gradle.listener.ListenerManager
+import spock.lang.Specification
+import org.gradle.api.artifacts.*
+
+class DefaultConfigurationSpec extends Specification {
+
+    ConfigurationsProvider configurationsProvider = Mock()
+    ArtifactDependencyResolver dependencyResolver = Mock()
+    ListenerManager listenerManager = Mock()
+    DependencyMetaDataProvider metaDataProvider = Mock()
+    ResolutionStrategyInternal resolutionStrategy = Mock()
+
+    DefaultConfiguration conf(String confName = "conf", String path = ":conf") {
+        new DefaultConfiguration(path, confName, configurationsProvider, dependencyResolver, listenerManager, metaDataProvider, resolutionStrategy)
+    }
+
+    DefaultPublishArtifact artifact(String name) {
+        artifact(name: name)
+    }
+
+    DefaultPublishArtifact artifact(Map props = [:]) {
+        new DefaultPublishArtifact(
+            props.name ?: "artifact",
+            props.extension ?: "artifact",
+            props.type,
+            props.classifier,
+            props.date,
+            props.file,
+            props.tasks ?: []
+        )
+    }
+
+    // You need to wrap this in an interaction {} block when calling it
+    ResolvedConfiguration resolvedConfiguration(Configuration config, ArtifactDependencyResolver dependencyResolver = dependencyResolver) {
+        ResolvedConfiguration resolvedConfiguration = Mock()
+        1 * dependencyResolver.resolve(config) >> new ResolverResults(resolvedConfiguration, Mock(ResolutionResult))
+        resolvedConfiguration
+    }
+
+    def setup() {
+        ListenerBroadcast<DependencyResolutionListener> broadcast = new ListenerBroadcast<DependencyResolutionListener>(DependencyResolutionListener)
+        _ * listenerManager.createAnonymousBroadcaster(DependencyResolutionListener) >> broadcast
+    }
+
+    def "all artifacts collection has immediate artifacts"() {
+        given:
+        def c = conf()
+
+        when:
+        c.artifacts << artifact()
+        c.artifacts << artifact()
+
+        then:
+        c.allArtifacts.size() == 2
+    }
+
+    def "all artifacts collection has inherited artifacts"() {
+        given:
+        def master = conf()
+
+        def masterParent1 = conf()
+        def masterParent2 = conf()
+        master.extendsFrom masterParent1, masterParent2
+
+        def masterParent1Parent1 = conf()
+        def masterParent1Parent2 = conf()
+        masterParent1.extendsFrom masterParent1Parent1, masterParent1Parent2
+
+        def masterParent2Parent1 = conf()
+        def masterParent2Parent2 = conf()
+        masterParent2.extendsFrom masterParent2Parent1, masterParent2Parent2
+
+        def allArtifacts = master.allArtifacts
+
+        def added = []
+        allArtifacts.whenObjectAdded { added << it.name }
+        def removed = []
+        allArtifacts.whenObjectRemoved { removed << it.name }
+
+        expect:
+        allArtifacts.empty
+
+        when:
+        masterParent1.artifacts << artifact("p1-1")
+        masterParent1Parent1.artifacts << artifact("p1p1-1")
+        masterParent1Parent2.artifacts << artifact("p1p2-1")
+        masterParent2.artifacts << artifact("p2-1")
+        masterParent2Parent1.artifacts << artifact("p2p1-1")
+        masterParent2Parent2.artifacts << artifact("p2p2-1")
+
+        then:
+        allArtifacts.size() == 6
+        added == ["p1-1", "p1p1-1", "p1p2-1", "p2-1", "p2p1-1", "p2p2-1"]
+
+        when:
+        masterParent2Parent2.artifacts.remove masterParent2Parent2.artifacts.toList().first()
+
+        then:
+        allArtifacts.size() == 5
+        removed == ["p2p2-1"]
+
+        when:
+        removed.clear()
+        masterParent1.extendsFrom = []
+
+        then:
+        allArtifacts.size() == 3
+        removed == ["p1p1-1", "p1p2-1"]
+    }
+
+    def "incoming dependencies set has same name and path as owner configuration"() {
+        def config = conf("conf", ":path")
+
+        expect:
+        config.incoming.name == "conf"
+        config.incoming.path == ":path"
+    }
+
+    def "incoming dependencies set contains immediate dependencies"() {
+        def config = conf("conf")
+        Dependency dep1 = Mock()
+
+        given:
+        config.dependencies.add(dep1)
+
+        expect:
+        config.incoming.dependencies as List == [dep1]
+    }
+
+    def "incoming dependencies set contains inherited dependencies"() {
+        def parent = conf("conf")
+        def config = conf("conf")
+        Dependency dep1 = Mock()
+
+        given:
+        config.extendsFrom parent
+        parent.dependencies.add(dep1)
+
+        expect:
+        config.incoming.dependencies as List == [dep1]
+    }
+
+    def "incoming dependencies set files are resolved lazily"() {
+        setup:
+        def config = conf("conf")
+
+        when:
+        def files = config.incoming.files
+
+        then:
+        0 * _._
+
+        when:
+        files.files
+
+        then:
+        interaction { resolvedConfiguration(config) }
+        0 * dependencyResolver._
+    }
+
+    def "incoming dependencies set depends on all self resolving dependencies"() {
+        SelfResolvingDependency dependency = Mock()
+        Task task = Mock()
+        TaskDependency taskDep = Mock()
+        def config = conf("conf")
+
+        given:
+        config.dependencies.add(dependency)
+
+        when:
+        def depTaskDeps = config.incoming.dependencies.buildDependencies.getDependencies(null)
+        def fileTaskDeps = config.incoming.files.buildDependencies.getDependencies(null)
+
+        then:
+        depTaskDeps == [task] as Set
+        fileTaskDeps == [task] as Set
+        _ * dependency.buildDependencies >> taskDep
+        _ * taskDep.getDependencies(_) >> ([task] as Set)
+        0 * _._
+    }
+
+    def "notifies beforeResolve action on incoming dependencies set when dependencies are resolved"() {
+        Action<ResolvableDependencies> action = Mock()
+        def config = conf("conf")
+
+        given:
+        config.incoming.beforeResolve(action)
+
+        when:
+        config.resolvedConfiguration
+
+        then:
+        interaction { resolvedConfiguration(config) }
+        1 * action.execute(config.incoming)
+    }
+
+    def "calls beforeResolve closure on incoming dependencies set when dependencies are resolved"() {
+        def config = conf("conf")
+        resolvedConfiguration(config)
+        def called = false
+
+        expect:
+        config.incoming.afterResolve {
+            assert it == config.incoming
+            called = true
+        }
+
+        when:
+        config.resolvedConfiguration
+
+        then:
+        called
+    }
+
+    def "notifies afterResolve action on incoming dependencies set when dependencies are resolved"() {
+        Action<ResolvableDependencies> action = Mock()
+        def config = conf("conf")
+
+        given:
+        config.incoming.afterResolve(action)
+
+        when:
+        config.resolvedConfiguration
+
+        then:
+        interaction { resolvedConfiguration(config) }
+        1 * action.execute(config.incoming)
+
+    }
+
+    def "calls afterResolve closure on incoming dependencies set when dependencies are resolved"() {
+        def config = conf("conf")
+        resolvedConfiguration(config)
+        def called = false
+
+        expect:
+        config.incoming.afterResolve {
+            assert it == config.incoming
+            called = true
+        }
+
+        when:
+        config.resolvedConfiguration
+
+        then:
+        called
+    }
+    
+    def "a recursive copy of a configuration includes inherited exclude rules"() {
+        given:
+        def (p1, p2, child) = [conf("p1"), conf("p2"), conf("child")]
+        child.extendsFrom p1, p2
+        
+        and:
+        def (p1Exclude, p2Exclude) = [[group: 'p1', module: 'p1'], [group: 'p2', module: 'p2']]
+        p1.exclude p1Exclude
+        p2.exclude p2Exclude
+        
+        when:
+        def copied = child.copyRecursive()
+        
+        then:
+        copied.excludeRules.size() == 2
+        copied.excludeRules.collect{[group: it.group, module: it.module]}.sort { it.group } == [p1Exclude, p2Exclude]
+    }
+
+    def "copied configuration has own instance of resolution strategy"() {
+        def strategy = Mock(ResolutionStrategyInternal)
+        def conf = conf()
+
+        when:
+        def copy = conf.copy()
+
+        then:
+        1 * resolutionStrategy.copy() >> strategy
+        conf.resolutionStrategy != copy.resolutionStrategy
+        copy.resolutionStrategy == strategy
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java
new file mode 100644
index 0000000..9ad59ca
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java
@@ -0,0 +1,955 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.configurations;
+
+import groovy.lang.Closure;
+import org.gradle.api.GradleException;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.*;
+import org.gradle.api.artifacts.result.ResolutionResult;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.artifacts.*;
+import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.DefaultResolutionStrategy;
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.api.tasks.TaskContainer;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.listener.ListenerBroadcast;
+import org.gradle.listener.ListenerManager;
+import org.gradle.util.*;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.*;
+
+import static org.gradle.util.Matchers.hasSameItems;
+import static org.gradle.util.Matchers.isEmpty;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class DefaultConfigurationTest {
+    private JUnit4Mockery context = new JUnit4GroovyMockery();
+    private ArtifactDependencyResolver dependencyResolver = context.mock(ArtifactDependencyResolver.class);
+    private ConfigurationsProvider configurationContainer;
+    private ListenerManager listenerManager = context.mock(ListenerManager.class);
+    private DependencyMetaDataProvider metaDataProvider = context.mock(DependencyMetaDataProvider.class);
+    private DefaultConfiguration configuration;
+    private DependencyResolutionListener dependencyResolutionBroadcast = context.mock(DependencyResolutionListener.class);
+    private ListenerBroadcast resolutionListenerBroadcast = context.mock(ListenerBroadcast.class); 
+
+    @Before
+    public void setUp() {
+        configurationContainer = context.mock(ConfigurationsProvider.class);
+        context.checking(new Expectations(){{
+            allowing(listenerManager).createAnonymousBroadcaster(DependencyResolutionListener.class);
+            will(returnValue(resolutionListenerBroadcast));
+            allowing(resolutionListenerBroadcast).getSource();
+            will(returnValue(dependencyResolutionBroadcast));
+            allowing(dependencyResolutionBroadcast).afterResolve(with(any(ResolvableDependencies.class)));
+            allowing(dependencyResolutionBroadcast).beforeResolve(with(any(ResolvableDependencies.class)));
+            will(returnValue(null));
+        }});
+        configuration = createNamedConfiguration("path", "name");
+    }
+
+    @Test
+    public void defaultValues() {
+        assertThat(configuration.getName(), equalTo("name"));
+        assertThat(configuration.isVisible(), equalTo(true));
+        assertThat(configuration.getExtendsFrom().size(), equalTo(0));
+        assertThat(configuration.isTransitive(), equalTo(true));
+        assertThat(configuration.getDescription(), nullValue());
+        assertThat(configuration.getState(), equalTo(Configuration.State.UNRESOLVED));
+        assertThat(configuration.getDisplayName(), equalTo("configuration 'path'"));
+    }
+
+    @Test
+    public void hasUsefulDisplayName() {
+        assertThat(configuration.getDisplayName(), equalTo("configuration 'path'"));
+        assertThat(configuration.toString(), equalTo("configuration 'path'"));
+        assertThat(configuration.getIncoming().toString(), equalTo("dependencies 'path'"));
+    }
+
+    @Test
+    public void withPrivateVisibility() {
+        configuration.setVisible(false);
+        assertFalse(configuration.isVisible());
+    }
+
+    @Test
+    public void withIntransitive() {
+        configuration.setTransitive(false);
+        assertFalse(configuration.isTransitive());
+    }
+
+    @Test
+    public void exclude() {
+        Map<String, String> excludeArgs1 = toMap("group", "aGroup");
+        Map<String, String> excludeArgs2 = toMap("module", "aModule");
+        assertThat(configuration.exclude(excludeArgs1), sameInstance(configuration));
+        configuration.exclude(excludeArgs2);
+        assertThat(configuration.getExcludeRules(), equalTo(WrapUtil.<ExcludeRule>toSet(
+                new DefaultExcludeRule("aGroup", null), new DefaultExcludeRule(null, "aModule"))));
+    }
+
+    @Test
+    public void setExclude() {
+        Set<ExcludeRule> excludeRules = WrapUtil.<ExcludeRule>toSet(new DefaultExcludeRule("groupValue", null));
+        configuration.setExcludeRules(excludeRules);
+        assertThat(configuration.getExcludeRules(), equalTo(excludeRules));
+    }
+
+    @Test
+    public void withDescription() {
+        configuration.setDescription("description");
+        assertThat(configuration.getDescription(), equalTo("description"));
+    }
+
+    @Test
+    public void extendsOtherConfigurations() {
+        Configuration configuration1 = createNamedConfiguration("otherConf1");
+        configuration.extendsFrom(configuration1);
+        assertThat(configuration.getExtendsFrom(), equalTo(toSet(configuration1)));
+
+        Configuration configuration2 = createNamedConfiguration("otherConf2");
+        configuration.extendsFrom(configuration2);
+        assertThat(configuration.getExtendsFrom(), equalTo(toSet(configuration1, configuration2)));
+    }
+
+    @Test
+    public void setExtendsFrom() {
+        Configuration configuration1 = createNamedConfiguration("otherConf1");
+
+        configuration.setExtendsFrom(toSet(configuration1));
+        assertThat(configuration.getExtendsFrom(), equalTo(toSet(configuration1)));
+
+        Configuration configuration2 = createNamedConfiguration("otherConf2");
+        configuration.setExtendsFrom(toSet(configuration2));
+        assertThat(configuration.getExtendsFrom(), equalTo(toSet(configuration2)));
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void extendsFromWithDirectCycleShouldThrowInvalidUserDataEx() {
+        Configuration otherConfiguration = createNamedConfiguration("otherConf");
+        otherConfiguration.extendsFrom(configuration);
+        configuration.extendsFrom(otherConfiguration);
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void extendsFromWithIndirectCycleShouldThrowInvalidUserDataEx() {
+        Configuration otherConfiguration1 = createNamedConfiguration("otherConf1");
+        Configuration otherConfiguration2 = createNamedConfiguration("otherConf2");
+        configuration.extendsFrom(otherConfiguration1);
+        otherConfiguration1.extendsFrom(otherConfiguration2);
+        otherConfiguration2.extendsFrom(configuration);
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void setExtendsFromWithCycleShouldThrowInvalidUserDataEx() {
+        Configuration otherConfiguration = createNamedConfiguration("otherConf");
+        otherConfiguration.extendsFrom(configuration);
+        configuration.setExtendsFrom(toSet(otherConfiguration));
+    }
+
+    @Test
+    public void getHierarchy() {
+        Configuration root1 = createNamedConfiguration("root1");
+        Configuration middle1 = createNamedConfiguration("middle1").extendsFrom(root1);
+        Configuration root2 = createNamedConfiguration("root2");
+        Configuration middle2 = createNamedConfiguration("middle2").extendsFrom(root1, root2);
+        createNamedConfiguration("root3");
+        Configuration leaf = createNamedConfiguration("leaf1").extendsFrom(middle1, middle2);
+        Set<Configuration> hierarchy = leaf.getHierarchy();
+        assertThat(hierarchy.size(), equalTo(5));
+        assertThat(hierarchy.iterator().next(), equalTo(leaf));
+        assertBothExistsAndOneIsBeforeOther(hierarchy, middle1, root1);
+        assertBothExistsAndOneIsBeforeOther(hierarchy, middle2, root2);
+    }
+
+    private void assertBothExistsAndOneIsBeforeOther(Set<Configuration> hierarchy, Configuration beforeConf, Configuration afterConf) {
+        assertThat(hierarchy, hasItem(beforeConf));
+        assertThat(hierarchy, hasItem(afterConf));
+
+        boolean foundBeforeConf = false;
+        for (Configuration configuration : hierarchy) {
+            if (configuration.equals(beforeConf)) {
+                foundBeforeConf = true;
+            }
+            if (configuration.equals(afterConf)) {
+                assertThat(foundBeforeConf, equalTo(true));
+            }
+        }
+    }
+
+    @Test
+    public void getAll() {
+        final Configuration conf1 = createNamedConfiguration("testConf1");
+        final Configuration conf2 = createNamedConfiguration("testConf2");
+        context.checking(new Expectations(){{
+            one(configurationContainer).getAll();
+            will(returnValue(toSet(conf1, conf2)));
+        }});
+        assertThat(configuration.getAll(), equalTo(toSet(conf1, conf2)));
+    }
+
+    @Test(expected = GradleException.class)
+    public void getAsPathShouldRethrownFailure() {
+        prepareForResolveWithErrors();
+        configuration.resolve();
+    }
+
+    @Test
+    public void resolve() {
+        final Set<File> fileSet = toSet(new File("somePath"));
+        makeResolveReturnFileSet(fileSet);
+        assertThat(configuration.resolve(), equalTo(fileSet));
+        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
+    }
+
+    @Test
+    public void filesWithDependencies() {
+        final Set<File> fileSet = toSet(new File("somePath"));
+        prepareForFilesBySpec(fileSet);
+        assertThat(configuration.files(context.mock(Dependency.class)), equalTo(fileSet));
+        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
+    }
+
+    @Test
+    public void fileCollectionWithDependencies() {
+        Dependency dependency1 = HelperUtil.createDependency("group1", "name", "version");
+        Dependency dependency2 = HelperUtil.createDependency("group2", "name", "version");
+        DefaultConfiguration.ConfigurationFileCollection fileCollection = (DefaultConfiguration.ConfigurationFileCollection)
+                configuration.fileCollection(dependency1);
+        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(dependency1),
+                equalTo(true));
+        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(dependency2),
+                equalTo(false));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void filesWithSpec() {
+        final Set<File> fileSet = toSet(new File("somePath"));
+        prepareForFilesBySpec(fileSet);
+        assertThat(configuration.files(context.mock(Spec.class)), equalTo(fileSet));
+        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
+    }
+
+
+    @Test
+    public void fileCollectionWithSpec() {
+        @SuppressWarnings("unchecked")
+        Spec<Dependency> spec = context.mock(Spec.class);
+        DefaultConfiguration.ConfigurationFileCollection fileCollection = (DefaultConfiguration.ConfigurationFileCollection)
+                configuration.fileCollection(spec);
+        assertThat(fileCollection.getDependencySpec(), sameInstance((Object)spec));
+    }
+
+    @Test
+    public void filesWithClosureSpec() {
+        Closure closure = HelperUtil.toClosure("{ dep -> dep.group == 'group1' }");
+        final Set<File> fileSet = toSet(new File("somePath"));
+        prepareForFilesBySpec(fileSet);
+        assertThat(configuration.files(closure), equalTo(fileSet));
+        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
+    }
+
+    @Test
+    public void fileCollectionWithClosureSpec() {
+        Closure closure = HelperUtil.toClosure("{ dep -> dep.group == 'group1' }");
+        DefaultConfiguration.ConfigurationFileCollection fileCollection = (DefaultConfiguration.ConfigurationFileCollection)
+                configuration.fileCollection(closure);
+        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(HelperUtil.createDependency("group1", "name", "version")),
+                equalTo(true));
+        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(HelperUtil.createDependency("group2", "name", "version")),
+                equalTo(false));
+    }
+
+    @SuppressWarnings("unchecked")
+    private void prepareForFilesBySpec(final Set<File> fileSet) {
+        final ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
+        prepareResolve(resolvedConfiguration, false);
+        context.checking(new Expectations() {{
+            one(resolvedConfiguration).getFiles(with(any(Spec.class)));
+            will(returnValue(fileSet));
+        }});
+    }
+
+    @Test(expected = GradleException.class)
+    public void resolveShouldRethrowFailure() {
+        prepareForResolveWithErrors();
+        configuration.resolve();
+    }
+
+    private void prepareForResolveWithErrors() {
+        final ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
+        prepareResolve(resolvedConfiguration, true);
+        context.checking(new Expectations(){{
+            one(resolvedConfiguration).rethrowFailure();
+            will(throwException(new GradleException()));
+        }});
+    }
+
+    private void makeResolveReturnFileSet(final Set<File> fileSet) {
+        final ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
+        context.checking(new Expectations() {{
+            prepareResolve(resolvedConfiguration, false);
+            allowing(resolvedConfiguration).getFiles(Specs.SATISFIES_ALL);
+            will(returnValue(fileSet));
+        }});
+    }
+
+    @Test
+    public void resolveSuccessfullyAsResolvedConfiguration() {
+        ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
+        prepareResolve(resolvedConfiguration, false);
+        assertThat(configuration.getResolvedConfiguration(), equalTo(resolvedConfiguration));
+        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
+    }
+
+    private void prepareResolve(final ResolvedConfiguration resolvedConfiguration, final boolean withErrors) {
+        context.checking(new Expectations() {{
+            allowing(dependencyResolver).resolve(configuration);
+            will(returnValue(new ResolverResults(resolvedConfiguration, context.mock(ResolutionResult.class))));
+            allowing(resolvedConfiguration).hasError();
+            will(returnValue(withErrors));
+        }});
+    }
+
+    @Test
+    public void multipleResolvesShouldUseCachedResult() {
+        prepareResolve(context.mock(ResolvedConfiguration.class), true);
+        assertThat(configuration.getResolvedConfiguration(), sameInstance(configuration.getResolvedConfiguration()));
+    }
+
+    @Test
+    public void uploadTaskName() {
+        assertThat(configuration.getUploadTaskName(), equalTo("uploadName"));
+    }
+
+    private DefaultConfiguration createNamedConfiguration(String confName) {
+        return new DefaultConfiguration(confName, confName, configurationContainer,
+                dependencyResolver, listenerManager, metaDataProvider, new DefaultResolutionStrategy());
+    }
+    
+    private DefaultConfiguration createNamedConfiguration(String path, String confName) {
+        return new DefaultConfiguration(path, confName, configurationContainer,
+                dependencyResolver, listenerManager, metaDataProvider, new DefaultResolutionStrategy());
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void buildArtifacts() {
+        final Task otherConfTaskMock = context.mock(Task.class, "otherConfTask");
+        final Task artifactTaskMock = context.mock(Task.class, "artifactTask");
+        final Configuration otherConfiguration = context.mock(Configuration.class);
+        final TaskDependency otherArtifactTaskDependencyMock = context.mock(TaskDependency.class, "otherConfTaskDep");
+        final PublishArtifact otherArtifact = context.mock(PublishArtifact.class, "otherArtifact");
+        final PublishArtifactSet inheritedArtifacts = new DefaultPublishArtifactSet("artifacts", toDomainObjectSet(PublishArtifact.class, otherArtifact));
+        DefaultPublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
+        artifact.builtBy(artifactTaskMock);
+        configuration.getArtifacts().add(artifact);
+
+        context.checking(new Expectations() {{
+            allowing(otherConfiguration).getHierarchy();
+            will(returnValue(toSet()));
+
+            allowing(otherConfiguration).getAllArtifacts();
+            will(returnValue(inheritedArtifacts));
+
+            allowing(otherConfiguration).getAllDependencies();
+
+            allowing(otherArtifact).getBuildDependencies();
+            will(returnValue(otherArtifactTaskDependencyMock));
+            
+            allowing(otherArtifactTaskDependencyMock).getDependencies(with(any(Task.class)));
+            will(returnValue(toSet(otherConfTaskMock)));
+        }});
+        configuration.setExtendsFrom(toSet(otherConfiguration));
+        assertThat((Set<Task>) configuration.getAllArtifacts().getBuildDependencies().getDependencies(context.mock(Task.class, "caller")),
+                equalTo(toSet(artifactTaskMock, otherConfTaskMock)));
+    }
+
+    @Test
+    public void getAllArtifactFiles() {
+        final Task otherConfTaskMock = context.mock(Task.class, "otherConfTask");
+        final Task artifactTaskMock = context.mock(Task.class, "artifactTask");
+        final Configuration otherConfiguration = context.mock(Configuration.class);
+        final TaskDependency otherConfTaskDependencyMock = context.mock(TaskDependency.class, "otherConfTaskDep");
+        final TaskDependency artifactTaskDependencyMock = context.mock(TaskDependency.class, "artifactTaskDep");
+        final File artifactFile1 = new File("artifact1");
+        final File artifactFile2 = new File("artifact2");
+        final PublishArtifact artifact = context.mock(PublishArtifact.class, "artifact");
+        final PublishArtifact otherArtifact = context.mock(PublishArtifact.class, "otherArtifact");
+        final PublishArtifactSet otherArtifacts = new DefaultPublishArtifactSet("artifacts", toDomainObjectSet(PublishArtifact.class, otherArtifact));
+
+        context.checking(new Expectations() {{
+            allowing(otherConfiguration).getHierarchy();
+            will(returnValue(toSet()));
+
+            allowing(otherConfiguration).getAllArtifacts();
+            will(returnValue(otherArtifacts));
+
+            allowing(otherConfiguration).getAllDependencies();
+
+            allowing(otherConfiguration).getExtendsFrom();
+            will(returnValue(toSet()));
+
+            allowing(otherConfiguration).getArtifacts();
+            will(returnValue(toSet(otherArtifact)));
+
+            allowing(otherConfTaskDependencyMock).getDependencies(with(any(Task.class)));
+            will(returnValue(toSet(otherConfTaskMock)));
+
+            allowing(artifactTaskDependencyMock).getDependencies(with(any(Task.class)));
+            will(returnValue(toSet(artifactTaskMock)));
+
+            allowing(artifact).getFile();
+            will(returnValue(artifactFile1));
+
+            allowing(otherArtifact).getFile();
+            will(returnValue(artifactFile2));
+
+            allowing(artifact).getBuildDependencies();
+            will(returnValue(artifactTaskDependencyMock));
+
+            allowing(otherArtifact).getBuildDependencies();
+            will(returnValue(otherConfTaskDependencyMock));
+        }});
+
+        configuration.getArtifacts().add(artifact);
+        configuration.setExtendsFrom(toSet(otherConfiguration));
+
+        FileCollection files = configuration.getAllArtifacts().getFiles();
+        assertThat(files.getFiles(), equalTo(toSet(artifactFile1, artifactFile2)));
+        assertThat(files.getBuildDependencies().getDependencies(null), equalTo((Set) toSet(otherConfTaskMock, artifactTaskMock)));
+    }
+
+    @Test
+    public void buildDependenciesDelegatesToAllSelfResolvingDependencies() {
+        final Task target = context.mock(Task.class, "target");
+        final Task projectDepTaskDummy = context.mock(Task.class, "projectDepTask");
+        final Task fileDepTaskDummy = context.mock(Task.class, "fileDepTask");
+        final ProjectDependency projectDependencyStub = context.mock(ProjectDependency.class);
+        final FileCollectionDependency fileCollectionDependencyStub = context.mock(FileCollectionDependency.class);
+
+        context.checking(new Expectations() {{
+            TaskDependency projectTaskDependencyDummy = context.mock(TaskDependency.class, "projectDep");
+            TaskDependency fileTaskDependencyStub = context.mock(TaskDependency.class, "fileDep");
+
+            allowing(projectDependencyStub).getBuildDependencies();
+            will(returnValue(projectTaskDependencyDummy));
+
+            allowing(projectTaskDependencyDummy).getDependencies(target);
+            will(returnValue(toSet(projectDepTaskDummy)));
+
+            allowing(fileCollectionDependencyStub).getBuildDependencies();
+            will(returnValue(fileTaskDependencyStub));
+
+            allowing(fileTaskDependencyStub).getDependencies(target);
+            will(returnValue(toSet(fileDepTaskDummy)));
+        }});
+
+        configuration.getDependencies().add(projectDependencyStub);
+        configuration.getDependencies().add(fileCollectionDependencyStub);
+
+        assertThat(configuration.getBuildDependencies().getDependencies(target), equalTo((Set) toSet(fileDepTaskDummy,
+                projectDepTaskDummy)));
+    }
+
+    @Test
+    public void buildDependenciesDelegatesToInheritedConfigurations() {
+        final Task target = context.mock(Task.class, "target");
+        final Task otherConfTaskMock = context.mock(Task.class, "otherConfTask");
+        final TaskDependency dependencyTaskDependencyStub = context.mock(TaskDependency.class, "otherConfTaskDep");
+        final Configuration otherConfiguration = context.mock(Configuration.class, "otherConf");
+        final FileCollectionDependency fileCollectionDependencyStub = context.mock(FileCollectionDependency.class);
+        final DependencySet inherited = new DefaultDependencySet("dependencies", toDomainObjectSet(Dependency.class, fileCollectionDependencyStub));
+
+        context.checking(new Expectations() {{
+            allowing(otherConfiguration).getHierarchy();
+            will(returnValue(toSet()));
+
+            allowing(otherConfiguration).getAllArtifacts();
+
+            allowing(otherConfiguration).getAllDependencies();
+            will(returnValue(inherited));
+
+            allowing(fileCollectionDependencyStub).getBuildDependencies();
+            will(returnValue(dependencyTaskDependencyStub));
+
+            allowing(dependencyTaskDependencyStub).getDependencies(target);
+            will(returnValue(toSet(otherConfTaskMock)));
+        }});
+
+        configuration.extendsFrom(otherConfiguration);
+
+        assertThat(configuration.getBuildDependencies().getDependencies(target), equalTo((Set) toSet(otherConfTaskMock)));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test public void taskDependencyFromProjectDependencyUsingNeeded() {
+        Configuration superConfig = createNamedConfiguration("superConf");
+        configuration.extendsFrom(superConfig);
+
+        final ProjectDependency projectDependencyStub = context.mock(ProjectDependency.class);
+        superConfig.getDependencies().add(projectDependencyStub);
+
+        final Project projectStub = context.mock(Project.class);
+        final TaskContainer taskContainerStub = context.mock(TaskContainer.class);
+        final Task taskStub = context.mock(Task.class);
+        final String taskName = "testit";
+
+        context.checking(new Expectations() {{
+            allowing(projectDependencyStub).getDependencyProject(); will(returnValue(projectStub));
+            allowing(projectStub).getTasks(); will(returnValue(taskContainerStub));
+            allowing(taskContainerStub).findByName(taskName); will(returnValue(taskStub));
+        }});
+
+        TaskDependency td = configuration.getTaskDependencyFromProjectDependency(true, taskName);
+        Task unusedTask = context.mock(Task.class, "unused");
+
+        assertThat((Set<Task>) td.getDependencies(unusedTask), equalTo(toSet(taskStub)));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test public void taskDependencyFromProjectDependencyUsingDependents() {
+        final String configName = configuration.getName();
+        final String taskName = "testit";
+        final Task tdTask = context.mock(Task.class, "tdTask");
+        final Project taskProject = context.mock(Project.class, "taskProject");
+        final Project rootProject = context.mock(Project.class, "rootProject");
+        final Project dependentProject = context.mock(Project.class, "dependentProject");
+        final Task desiredTask = context.mock(Task.class, "desiredTask");
+        final Set<Task> taskSet = toSet(desiredTask);
+        final ConfigurationContainer configurationContainer = context.mock(ConfigurationContainer.class);
+        final Configuration dependentConfig = context.mock(Configuration.class);
+        final ProjectDependency projectDependency = context.mock(ProjectDependency.class);
+        final Set<ProjectDependency> projectDependencies = toDomainObjectSet(ProjectDependency.class, projectDependency);
+        final DependencySet otherDependencies = context.mock(DependencySet.class);
+
+        context.checking(new Expectations() {{
+            allowing(tdTask).getProject(); will(returnValue(taskProject));
+            allowing(taskProject).getRootProject(); will(returnValue(rootProject));
+            allowing(rootProject).getTasksByName(taskName, true); will(returnValue(taskSet));
+            allowing(desiredTask).getProject(); will(returnValue(dependentProject));
+            allowing(dependentProject).getConfigurations(); will(returnValue(configurationContainer));
+            allowing(configurationContainer).findByName(configName); will(returnValue(dependentConfig));
+
+            allowing(dependentConfig).getAllDependencies(); will(returnValue(otherDependencies));
+            allowing(otherDependencies).withType(ProjectDependency.class); will(returnValue(projectDependencies));
+            allowing(projectDependency).getDependencyProject(); will(returnValue(taskProject));
+        }});
+
+        TaskDependency td = configuration.getTaskDependencyFromProjectDependency(false, taskName);
+        assertThat((Set<Task>) td.getDependencies(tdTask), equalTo(toSet(desiredTask)));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test public void taskDependencyFromProjectDependencyWithoutCommonConfiguration() {
+        // This test exists because a NullPointerException was thrown by
+        // getTaskDependencyFromProjectDependency() if the rootProject
+        // defined a task as the same name as a subproject's task, but did
+        // not define the same configuration.
+        final String configName = configuration.getName();
+        final String taskName = "testit";
+        final Task tdTask = context.mock(Task.class, "tdTask");
+        final Project taskProject = context.mock(Project.class, "taskProject");
+        final Project rootProject = context.mock(Project.class, "rootProject");
+        final Project dependentProject = context.mock(Project.class, "dependentProject");
+        final Task desiredTask = context.mock(Task.class, "desiredTask");
+        final Set<Task> taskSet = toSet(desiredTask);
+        final ConfigurationContainer configurationContainer = context.mock(ConfigurationContainer.class);
+
+        context.checking(new Expectations() {{
+            allowing(tdTask).getProject(); will(returnValue(taskProject));
+            allowing(taskProject).getRootProject(); will(returnValue(rootProject));
+            allowing(rootProject).getTasksByName(taskName, true); will(returnValue(taskSet));
+            allowing(desiredTask).getProject(); will(returnValue(dependentProject));
+            allowing(dependentProject).getConfigurations(); will(returnValue(configurationContainer));
+
+            // return null to mock not finding the given configuration
+            allowing(configurationContainer).findByName(configName); will(returnValue(null));
+        }});
+
+        TaskDependency td = configuration.getTaskDependencyFromProjectDependency(false, taskName);
+        assertThat(td.getDependencies(tdTask), equalTo(Collections.EMPTY_SET));
+    }
+
+
+    @Test
+    public void getDependencies() {
+        Dependency dependency = context.mock(Dependency.class);
+        configuration.getDependencies().add(dependency);
+        assertThat(configuration.getDependencies(), hasSameItems(toSet(dependency)));
+    }
+
+    @Test
+    public void getTypedDependencies() {
+        ProjectDependency projectDependency = context.mock(ProjectDependency.class);
+        configuration.getDependencies().add(context.mock(Dependency.class));
+        configuration.getDependencies().add(projectDependency);
+        assertThat(configuration.getDependencies().withType(ProjectDependency.class), hasSameItems(toSet(projectDependency)));
+    }
+
+    @Test
+    public void getTypedDependenciesReturnsEmptySetWhenNoMatches() {
+        configuration.getDependencies().add(context.mock(Dependency.class));
+        assertThat(configuration.getDependencies().withType(ProjectDependency.class), isEmpty());
+    }
+
+    @Test
+    public void getAllDependencies() {
+        Dependency dependencyConf = HelperUtil.createDependency("group1", "name1", "version1");
+        Dependency dependencyOtherConf1 = HelperUtil.createDependency("group1", "name1", "version1");
+        Dependency dependencyOtherConf2 = context.mock(Dependency.class, "dep2");
+        Configuration otherConf = createNamedConfiguration("otherConf");
+        configuration.getDependencies().add(dependencyConf);
+        configuration.extendsFrom(otherConf);
+        otherConf.getDependencies().add(dependencyOtherConf1);
+        otherConf.getDependencies().add(dependencyOtherConf2);
+
+        assertThat(configuration.getAllDependencies(), hasSameItems(toLinkedSet(dependencyConf, dependencyOtherConf2)));
+        assertCorrectInstanceInAllDependencies(configuration.getAllDependencies(), dependencyConf);
+    }
+
+    @Test
+    public void getAllTypedDependencies() {
+        ProjectDependency projectDependencyCurrentConf = context.mock(ProjectDependency.class, "projectDepCurrentConf");
+        configuration.getDependencies().add(context.mock(Dependency.class, "depCurrentConf"));
+        configuration.getDependencies().add(projectDependencyCurrentConf);
+        Configuration otherConf = createNamedConfiguration("otherConf");
+        configuration.extendsFrom(otherConf);
+        ProjectDependency projectDependencyExtendedConf = context.mock(ProjectDependency.class, "projectDepExtendedConf");
+        otherConf.getDependencies().add(context.mock(Dependency.class, "depExtendedConf"));
+        otherConf.getDependencies().add(projectDependencyExtendedConf);
+
+        assertThat(configuration.getAllDependencies().withType(ProjectDependency.class), hasSameItems(toLinkedSet(projectDependencyCurrentConf, projectDependencyExtendedConf)));
+    }
+
+    @Test
+    public void getAllTypedDependenciesReturnsEmptySetWhenNoMatches() {
+        configuration.getDependencies().add(context.mock(Dependency.class, "depCurrentConf"));
+        Configuration otherConf = createNamedConfiguration("otherConf");
+        configuration.extendsFrom(otherConf);
+        otherConf.getDependencies().add(context.mock(Dependency.class, "depExtendedConf"));
+
+        assertThat(configuration.getAllDependencies().withType(ProjectDependency.class), isEmpty());
+    }
+
+    @Test
+    public void getAllArtifacts() {
+        PublishArtifact artifactConf = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
+        PublishArtifact artifactOtherConf2 = HelperUtil.createPublishArtifact("name2", "ext2", "type2", "classifier2");
+        Configuration otherConf = createNamedConfiguration("otherConf");
+        configuration.getArtifacts().add(artifactConf);
+        configuration.extendsFrom(otherConf);
+        otherConf.getArtifacts().add(artifactOtherConf2);
+        assertThat(configuration.getAllArtifacts(), hasSameItems(toLinkedSet(artifactConf, artifactOtherConf2)));
+    }
+
+    @Test
+    public void artifactAddedAction() {
+        final TestClosure closure = context.mock(TestClosure.class);
+        final PublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
+
+        context.checking(new Expectations() {{
+            one(closure).call(artifact);
+        }});
+
+        configuration.getArtifacts().whenObjectAdded(HelperUtil.toClosure(closure));
+        configuration.getArtifacts().add(artifact);
+    }
+
+    @Test
+    public void artifactRemovedAction() {
+        final TestClosure closure = context.mock(TestClosure.class);
+        final PublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
+
+        configuration.getArtifacts().add(artifact);
+
+        context.checking(new Expectations() {{
+            one(closure).call(artifact);
+        }});
+
+        configuration.getArtifacts().whenObjectRemoved(HelperUtil.toClosure(closure));
+
+        configuration.getArtifacts().remove(artifact);
+    }
+
+    @Test
+    public void removeArtifact() {
+        PublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
+        configuration.getArtifacts().add(artifact);
+        configuration.getArtifacts().remove(artifact);
+        assertThat(configuration.getAllArtifacts(), Matchers.isEmpty());
+    }
+
+    @Test
+    public void removeArtifactWithUnknownArtifact() {
+        PublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
+        configuration.getArtifacts().add(artifact);
+        configuration.getArtifacts().remove(HelperUtil.createPublishArtifact("name2", "ext1", "type1", "classifier1"));
+        assertThat(configuration.getAllArtifacts(), hasSameItems(toSet(artifact)));
+    }
+
+    private void assertCorrectInstanceInAllDependencies(Set<Dependency> allDependencies, Dependency correctInstance) {
+        for (Dependency dependency : allDependencies) {
+            if (dependency == correctInstance) {
+                return;
+            }
+        }
+        fail("Correct instance is missing!");
+    }
+
+    @Test
+    public void copy() {
+        prepareConfigurationForCopyTest();
+
+        Configuration copiedConfiguration = configuration.copy();
+
+        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, configuration.getDependencies());
+    }
+
+    @Test
+    public void copyWithSpec() {
+        prepareConfigurationForCopyTest();
+        Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getDependencies());
+        configuration.getDependencies().add(HelperUtil.createDependency("group3", "name3", "version3"));
+
+        Configuration copiedConfiguration = configuration.copy(new Spec<Dependency>() {
+            public boolean isSatisfiedBy(Dependency element) {
+                return !element.getGroup().equals("group3");
+            }
+        });
+
+        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, expectedDependenciesToCopy);
+    }
+
+    @Test
+    public void copyWithClosure() {
+        prepareConfigurationForCopyTest();
+        Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getDependencies());
+        configuration.getDependencies().add(HelperUtil.createDependency("group3", "name3", "version3"));
+
+        Closure specClosure = HelperUtil.toClosure("{ element ->  !element.group.equals(\"group3\")}");
+        Configuration copiedConfiguration = configuration.copy(specClosure);
+
+        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, expectedDependenciesToCopy);
+    }
+
+    private void prepareConfigurationForCopyTest() {
+        configuration.setVisible(false);
+        configuration.setTransitive(false);
+        configuration.setDescription("descript");
+        configuration.exclude(toMap("group", "value"));
+        configuration.exclude(toMap("group", "value2"));
+        configuration.getArtifacts().add(HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1"));
+        configuration.getArtifacts().add(HelperUtil.createPublishArtifact("name2", "ext2", "type2", "classifier2"));
+        configuration.getDependencies().add(HelperUtil.createDependency("group1", "name1", "version1"));
+        configuration.getDependencies().add(HelperUtil.createDependency("group2", "name2", "version2"));
+    }
+
+    private void assertThatCopiedConfigurationHasElementsAndName(Configuration copiedConfiguration, Set<Dependency> expectedDependencies) {
+        assertThat(copiedConfiguration.getName(), equalTo(configuration.getName() + "Copy"));
+        assertThat(copiedConfiguration.isVisible(), equalTo(configuration.isVisible()));
+        assertThat(copiedConfiguration.isTransitive(), equalTo(configuration.isTransitive()));
+        assertThat(copiedConfiguration.getDescription(), equalTo(configuration.getDescription()));
+        assertThat(asSet(copiedConfiguration.getAllArtifacts()), equalTo(asSet(configuration.getAllArtifacts())));
+        assertThat(copiedConfiguration.getExcludeRules(), equalTo(configuration.getExcludeRules()));
+        assertThat(copiedConfiguration.getExcludeRules().iterator().next(), not(sameInstance(configuration.getExcludeRules().iterator().next())));
+        assertThat(WrapUtil.asSet(copiedConfiguration.getDependencies()), equalTo(WrapUtil.asSet(expectedDependencies)));
+        assertNotSameInstances(copiedConfiguration.getDependencies(), expectedDependencies);
+    }
+
+    @Test
+    public void copyRecursive() {
+        prepareConfigurationForCopyRecursiveTest();
+
+        Configuration copiedConfiguration = configuration.copyRecursive();
+
+        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, configuration.getAllDependencies());
+    }
+
+    @Test
+    public void copyRecursiveWithSpec() {
+        prepareConfigurationForCopyRecursiveTest();
+        Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getAllDependencies());
+        configuration.getDependencies().add(HelperUtil.createDependency("group3", "name3", "version3"));
+
+        Closure specClosure = HelperUtil.toClosure("{ element ->  !element.group.equals(\"group3\")}");
+        Configuration copiedConfiguration = configuration.copyRecursive(specClosure);
+
+        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, expectedDependenciesToCopy);
+    }
+
+    @Test
+    public void copyRecursiveWithClosure() {
+        prepareConfigurationForCopyRecursiveTest();
+        Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getAllDependencies());
+        configuration.getDependencies().add(HelperUtil.createDependency("group3", "name3", "version3"));
+
+        Configuration copiedConfiguration = configuration.copyRecursive(new Spec<Dependency>() {
+            public boolean isSatisfiedBy(Dependency element) {
+                return !element.getGroup().equals("group3");
+            }
+        });
+
+        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, expectedDependenciesToCopy);
+    }
+
+    private void prepareConfigurationForCopyRecursiveTest() {
+        prepareConfigurationForCopyTest();
+        Dependency similarDependency2InOtherConf = HelperUtil.createDependency("group2", "name2", "version2");
+        Dependency otherConfDependency = HelperUtil.createDependency("group4", "name4", "version4");
+        Configuration otherConf = createNamedConfiguration("otherConf");
+        otherConf.getDependencies().add(similarDependency2InOtherConf);
+        otherConf.getDependencies().add(otherConfDependency);
+        configuration.extendsFrom(otherConf);
+    }
+
+    private void assertNotSameInstances(Set<Dependency> dependencies, Set<Dependency> otherDependencies) {
+        for (Dependency dependency : dependencies) {
+            assertHasEqualButNotSameInstance(dependency, otherDependencies);
+        }
+    }
+
+    private void assertHasEqualButNotSameInstance(Dependency dependency, Set<Dependency> otherDependencies) {
+        assertThat(otherDependencies, hasItem(dependency));
+        for (Dependency otherDependency : otherDependencies) {
+            if (otherDependency.equals(dependency)) {
+                assertThat(otherDependency, not(sameInstance(dependency)));
+            }
+        }
+    }
+
+    @Test
+    public void propertyChangeWithNonUnresolvedStateShouldThrowEx() {
+        makeResolveReturnFileSet(new HashSet<File>());
+        configuration.resolve();
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.setTransitive(true);
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.setDescription("someDesc");
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.setExcludeRules(new HashSet<ExcludeRule>());
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.setExtendsFrom(new HashSet<Configuration>());
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.setVisible(true);
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.getDependencies().add(context.mock(Dependency.class));
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.getDependencies().add(context.mock(Dependency.class));
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.exclude(new HashMap<String, String>());
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.extendsFrom(context.mock(Configuration.class));
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.getArtifacts().add(context.mock(PublishArtifact.class));
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.getArtifacts().remove(context.mock(PublishArtifact.class, "removeArtifact"));
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.getArtifacts().add(context.mock(PublishArtifact.class, "removeArtifact"));
+            }
+        });
+    }
+    
+    @Test
+    public void dumpString() {
+        Dependency configurationDependency = HelperUtil.createDependency("dumpgroup1", "dumpname1", "dumpversion1");
+        Dependency otherConfSimilarDependency = HelperUtil.createDependency("dumpgroup1", "dumpname1", "dumpversion1");
+        Dependency otherConfDependency = HelperUtil.createDependency("dumpgroup2", "dumpname2", "dumpversion2");
+        Configuration otherConf = createNamedConfiguration("dumpConf");
+        configuration.extendsFrom(otherConf);
+        otherConf.getDependencies().add(otherConfDependency);
+        otherConf.getDependencies().add(otherConfSimilarDependency);
+        configuration.getDependencies().add(configurationDependency);
+
+        assertThat(configuration.dump(),
+                containsString(
+                "\nConfiguration:"
+                + "  class='class org.gradle.api.internal.artifacts.configurations.DefaultConfiguration'"
+                + "  name='name'"
+                + "  hashcode='"+ configuration.hashCode() +"'"
+                + "\nLocal Dependencies:"
+                + "\n   DefaultExternalModuleDependency{group='dumpgroup1', name='dumpname1', version='dumpversion1', configuration='default'}"
+                + "\nLocal Artifacts:"
+                + "\n   none"
+                + "\nAll Dependencies:"
+                + "\n   DefaultExternalModuleDependency{group='dumpgroup1', name='dumpname1', version='dumpversion1', configuration='default'}"
+                + "\n   DefaultExternalModuleDependency{group='dumpgroup2', name='dumpname2', version='dumpversion2', configuration='default'}"
+                + "\nAll Artifacts:"
+                + "\n   none"));
+    }
+
+    private void assertInvalidUserDataException(Executer executer) {
+        try {
+            executer.execute();
+            fail();
+        } catch (InvalidUserDataException e) {
+            // ignore
+        }
+    }
+
+    private static interface Executer {
+        void execute();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ForcedModuleNotationParserSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ForcedModuleNotationParserSpec.groovy
new file mode 100644
index 0000000..5f5ef9c
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ForcedModuleNotationParserSpec.groovy
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.dsl;
+
+
+import org.gradle.api.InvalidUserDataException
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 10/14/11
+ */
+public class ForcedModuleNotationParserSpec extends Specification {
+
+    def "understands group:name:version notation"() {
+        when:
+        def v = new ForcedModuleNotationParser().parseNotation("org.foo:bar:1.0") as List
+
+        then:
+        v.size() == 1
+        v[0].group == 'org.foo'
+        v[0].name  == 'bar'
+        v[0].version  == '1.0'
+    }
+
+    def "works with CharSequences"() {
+        when:
+        def sb = new StringBuilder().append("org.foo:charsequence:1.0")
+        def v = new ForcedModuleNotationParser().parseNotation(sb) as List
+
+        then:
+        v.size() == 1
+        v[0].name  == 'charsequence'
+    }
+
+    def "allows exact type on input"() {
+        def id = ForcedModuleNotationParser.selector("org.foo", "bar", "2.0")
+
+        when:
+        def v = new ForcedModuleNotationParser().parseNotation(id) as List
+
+        then:
+        v.size() == 1
+        v[0].group == 'org.foo'
+        v[0].name  == 'bar'
+        v[0].version  == '2.0'
+    }
+
+    def "allows list of objects on input"() {
+        def id = ForcedModuleNotationParser.selector("org.foo", "bar", "2.0")
+
+        when:
+        def v = new ForcedModuleNotationParser().parseNotation([id, ["hey:man:1.0"], [group:'i', name:'like', version:'maps']]) as List
+
+        then:
+        v.size() == 3
+        v[0].name == 'bar'
+        v[1].name == 'man'
+        v[2].name == 'like'
+    }
+
+    def "allows map on input"() {
+        when:
+        def v = new ForcedModuleNotationParser().parseNotation([group: 'org.foo', name: 'bar', version:'1.0']) as List
+
+        then:
+        v.size() == 1
+        v[0].group == 'org.foo'
+        v[0].name  == 'bar'
+        v[0].version  == '1.0'
+    }
+
+    def "fails for unknown types"() {
+        when:
+        new ForcedModuleNotationParser().parseNotation(new Object())
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def "reports missing keys for map notation"() {
+        when:
+        new ForcedModuleNotationParser().parseNotation([name: "bar", version: "1.0"])
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def "reports wrong keys for map notation"() {
+        when:
+        new ForcedModuleNotationParser().parseNotation([groop: 'groop', name: "bar", version: "1.0"])
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def "reports invalid format for string notation"() {
+        when:
+        new ForcedModuleNotationParser().parseNotation(["blahblah"])
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def "reports invalid missing data for string notation"() {
+        when:
+        new ForcedModuleNotationParser().parseNotation([":foo:"])
+
+        then:
+        def ex = thrown(InvalidUserDataException)
+        ex.message.contains 'cannot be empty'
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactResolveResultTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactResolveResultTest.groovy
index f57f3d4..292b4bc 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactResolveResultTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactResolveResultTest.groovy
@@ -34,15 +34,6 @@ class DefaultBuildableArtifactResolveResultTest extends Specification {
         e.message == 'No result has been specified.'
     }
 
-    def "cannot get metadata when no result specified"() {
-        when:
-        result.externalResourceMetaData
-
-        then:
-        IllegalStateException e = thrown()
-        e.message == 'No result has been specified.'
-    }
-
     def "cannot get failure when no result specified"() {
         when:
         result.failure
@@ -64,18 +55,6 @@ class DefaultBuildableArtifactResolveResultTest extends Specification {
         e == failure
     }
 
-    def "cannot get metadata when resolve failed"() {
-        def failure = new ArtifactResolveException("broken")
-
-        when:
-        result.failed(failure)
-        result.externalResourceMetaData
-
-        then:
-        ArtifactResolveException e = thrown()
-        e == failure
-    }
-
     def "fails with not found exception when artifact not found"() {
         when:
         result.notFound(new DefaultArtifact(ModuleRevisionId.newInstance("org", "module", "rev"), new Date(), "art", "type", "type"))
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResultTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResultTest.groovy
index 360899a..bdf48e0 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResultTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResultTest.groovy
@@ -17,8 +17,9 @@
 package org.gradle.api.internal.artifacts.ivyservice
 
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor
-import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.gradle.api.artifacts.ModuleVersionIdentifier
 import spock.lang.Specification
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
 
 class DefaultBuildableModuleVersionResolveResultTest extends Specification {
     def result = new DefaultBuildableModuleVersionResolveResult()
@@ -60,7 +61,7 @@ class DefaultBuildableModuleVersionResolveResultTest extends Specification {
     }
 
     def "cannot get id when resolve failed"() {
-        def failure = new ModuleVersionResolveException("broken")
+        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
 
         when:
         result.failed(failure)
@@ -72,7 +73,7 @@ class DefaultBuildableModuleVersionResolveResultTest extends Specification {
     }
 
     def "cannot get descriptor when resolve failed"() {
-        def failure = new ModuleVersionResolveException("broken")
+        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
 
         when:
         result.failed(failure)
@@ -84,7 +85,7 @@ class DefaultBuildableModuleVersionResolveResultTest extends Specification {
     }
 
     def "cannot get artifact resolver when resolve failed"() {
-        def failure = new ModuleVersionResolveException("broken")
+        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
 
         when:
         result.failed(failure)
@@ -97,7 +98,7 @@ class DefaultBuildableModuleVersionResolveResultTest extends Specification {
 
     def "failure is null when successfully resolved"() {
         when:
-        result.resolved(Mock(ModuleRevisionId), Mock(ModuleDescriptor), Mock(ArtifactResolver))
+        result.resolved(Mock(ModuleVersionIdentifier), Mock(ModuleDescriptor), Mock(ArtifactResolver))
 
         then:
         result.failure == null
@@ -105,7 +106,7 @@ class DefaultBuildableModuleVersionResolveResultTest extends Specification {
 
     def "fails with a not found exception when not found"() {
         when:
-        result.notFound(Mock(ModuleRevisionId))
+        result.notFound(Mock(ModuleVersionIdentifier))
 
         then:
         result.failure instanceof ModuleVersionNotFoundException
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetailsSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetailsSpec.groovy
new file mode 100644
index 0000000..65cf1b8
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetailsSpec.groovy
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice
+
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
+
+/**
+ * by Szczepan Faber, created at: 12/13/12
+ */
+class DefaultDependencyResolveDetailsSpec extends Specification {
+
+    def "can specify version to use"() {
+        def requested = newSelector("org", "foo", "1.0")
+
+        when:
+        def details = new DefaultDependencyResolveDetails(requested)
+
+        then:
+        details.requested == requested
+        details.target == requested
+        !details.updated
+        !details.selectionReason
+
+        when:
+        details.useVersion("1.0") //the same version
+
+        then:
+        details.requested == requested
+        details.target == requested
+        details.updated
+        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
+
+        when:
+        details.useVersion("2.0") //different version
+
+        then:
+        details.requested == requested
+        details.target != requested
+        details.updated
+        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
+
+        details.target.version == "2.0"
+        details.target.name == requested.name
+        details.target.group == requested.group
+    }
+
+    def "can specify version with selection reason"() {
+        def requested = newSelector("org", "foo", "1.0")
+        def details = new DefaultDependencyResolveDetails(requested)
+
+        when:
+        details.useVersion("1.0", VersionSelectionReasons.FORCED) //same version
+
+        then:
+        details.requested == requested
+        details.target == requested
+        details.updated
+        details.selectionReason == VersionSelectionReasons.FORCED
+
+        when:
+        details.useVersion("3.0", VersionSelectionReasons.FORCED) //different version
+
+        then:
+        details.requested == requested
+        details.target.version == "3.0"
+        details.target.name == requested.name
+        details.target.group == requested.group
+        details.updated
+        details.selectionReason == VersionSelectionReasons.FORCED
+    }
+
+    def "can override version and selection reason"() {
+        def requested = newSelector("org", "foo", "1.0")
+        def details = new DefaultDependencyResolveDetails(requested)
+
+        when:
+        details.useVersion("2.0", VersionSelectionReasons.FORCED)
+        details.useVersion("3.0", VersionSelectionReasons.SELECTED_BY_RULE)
+
+        then:
+        details.requested == requested
+        details.target.version == "3.0"
+        details.target.name == requested.name
+        details.target.group == requested.group
+        details.updated
+        details.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
+    }
+
+    def "does not allow null version"() {
+        def details = new DefaultDependencyResolveDetails(newSelector("org", "foo", "1.0"))
+
+        when:
+        details.useVersion(null)
+
+        then:
+        thrown(IllegalArgumentException)
+
+        when:
+        details.useVersion(null, VersionSelectionReasons.SELECTED_BY_RULE)
+
+        then:
+        thrown(IllegalArgumentException)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactPublisherTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactPublisherTest.groovy
deleted file mode 100644
index 4b1c550..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactPublisherTest.groovy
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice;
-
-
-import org.gradle.api.artifacts.Module
-import org.gradle.api.artifacts.PublishException
-import org.gradle.api.artifacts.ResolveException
-import org.gradle.api.internal.artifacts.ArtifactPublisher
-import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
-import org.gradle.util.JUnit4GroovyMockery
-import org.jmock.integration.junit4.JMock
-import org.junit.Test
-import org.junit.runner.RunWith
-
-import static org.hamcrest.Matchers.equalTo
-import static org.hamcrest.Matchers.sameInstance
-import static org.junit.Assert.assertThat
-import static org.junit.Assert.fail
-
- at RunWith(JMock.class)
-public class ErrorHandlingArtifactPublisherTest {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery();
-    private final ArtifactPublisher artifactPublisherMock = context.mock(ArtifactPublisher.class);
-    private final ConfigurationInternal configurationMock = context.mock(ConfigurationInternal.class, "<config display name>");
-    private final Set configurations = Collections.singleton(configurationMock)
-    private final Module moduleMock = context.mock(Module)
-
-    private final RuntimeException failure = new RuntimeException();
-    private final ErrorHandlingArtifactPublisher ivyService = new ErrorHandlingArtifactPublisher(artifactPublisherMock);
-
-    @Test
-    public void publishDelegatesToBackingService() {
-        context.checking {
-            one(artifactPublisherMock).publish(moduleMock, configurations, null, null)
-        }
-
-        ivyService.publish(moduleMock, configurations, null, null)
-    }
-
-    @Test
-    public void wrapsPublishException() {
-        context.checking {
-            one(configurationMock).getName(); will(returnValue("name"))
-            one(artifactPublisherMock).publish(moduleMock, configurations, null, null)
-            will(throwException(failure))
-        }
-
-        try {
-            ivyService.publish(moduleMock, configurations, null, null)
-            fail()
-        }
-        catch(PublishException e) {
-            assertThat e.message, equalTo("Could not publish configuration: [name]")
-            assertThat(e.cause, sameInstance((Throwable) failure));
-        }
-    }
-
-    def assertFailsWithResolveException(Closure cl) {
-        try {
-            cl();
-            fail();
-        } catch (ResolveException e) {
-            assertThat(e.message, equalTo("Could not resolve all dependencies for <config display name>."));
-            assertThat(e.cause, sameInstance((Throwable) failure));
-        }
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisherTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisherTest.java
index 348b9b1..b042cab 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisherTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisherTest.java
@@ -22,7 +22,10 @@ import org.apache.ivy.core.settings.IvySettings;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.Module;
-import org.gradle.api.internal.artifacts.configurations.*;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.internal.artifacts.configurations.Configurations;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.DefaultResolutionStrategy;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.gradle.util.WrapUtil;
 import org.jmock.Expectations;
@@ -49,13 +52,13 @@ public class IvyBackedArtifactPublisherTest {
     private ModuleDescriptor publishModuleDescriptorDummy = context.mock(ModuleDescriptor.class);
     private ModuleDescriptor fileModuleDescriptorMock = context.mock(ModuleDescriptor.class);
     private DependencyMetaDataProvider dependencyMetaDataProviderMock = context.mock(DependencyMetaDataProvider.class);
-    private ResolverProvider resolverProvider = context.mock(ResolverProvider.class);
     private IvyFactory ivyFactoryStub = context.mock(IvyFactory.class);
     private SettingsConverter settingsConverterStub = context.mock(SettingsConverter.class);
     private IvyDependencyPublisher ivyDependencyPublisherMock = context.mock(IvyDependencyPublisher.class);
     private ModuleDescriptorConverter publishModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "publishConverter");
     private ModuleDescriptorConverter fileModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "fileConverter");
     private IvyModuleDescriptorWriter ivyModuleDescriptorWriterMock = context.mock(IvyModuleDescriptorWriter.class);
+    final List<DependencyResolver> publishResolversDummy = createPublishResolversDummy();
 
     @Test
     public void testPublish() throws IOException, ParseException {
@@ -64,7 +67,6 @@ public class IvyBackedArtifactPublisherTest {
         final ConfigurationInternal configuration = context.mock(ConfigurationInternal.class);
         final Set<Configuration> configurations = createConfiguration();
         final File someDescriptorDestination = new File("somePath");
-        final List<DependencyResolver> publishResolversDummy = createPublishResolversDummy();
         final Module moduleDummy = context.mock(Module.class, "moduleForResolve");
         final IvyBackedArtifactPublisher ivyService = createIvyService();
 
@@ -77,8 +79,6 @@ public class IvyBackedArtifactPublisherTest {
             will(returnValue(configurations));
             allowing(configuration).getModule();
             will(returnValue(moduleDummy));
-            allowing(resolverProvider).getResolvers();
-            will(returnValue(publishResolversDummy));
             allowing(configuration).getResolutionStrategy();
             will(returnValue(new DefaultResolutionStrategy()));
             one(ivyDependencyPublisherMock).publish(expectedConfigurations,
@@ -86,17 +86,15 @@ public class IvyBackedArtifactPublisherTest {
             allowing(ivyModuleDescriptorWriterMock).write(fileModuleDescriptorMock, someDescriptorDestination, null);
         }});
 
-        ivyService.publish(configuration.getModule(), configuration.getHierarchy(), someDescriptorDestination, null);
+        ivyService.publish(publishResolversDummy, configuration.getModule(), configuration.getHierarchy(), someDescriptorDestination);
     }
 
     private IvyBackedArtifactPublisher createIvyService() {
-        return new IvyBackedArtifactPublisher(resolverProvider,
+        return new IvyBackedArtifactPublisher(
                 settingsConverterStub,
                 publishModuleDescriptorConverter,
-                fileModuleDescriptorConverter,
                 ivyFactoryStub,
-                ivyDependencyPublisherMock,
-                ivyModuleDescriptorWriterMock);
+                ivyDependencyPublisherMock);
     }
 
     private List<DependencyResolver> createPublishResolversDummy() {
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriterTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriterTest.groovy
index 8c44bee..345e62a 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriterTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriterTest.groovy
@@ -23,8 +23,8 @@ import org.apache.ivy.core.module.descriptor.ModuleDescriptor
 import org.apache.ivy.core.module.id.ModuleRevisionId
 import org.gradle.api.Action
 import org.gradle.api.XmlProvider
-import org.gradle.api.internal.XmlTransformer
-import org.gradle.util.TemporaryFolder
+import org.gradle.api.internal.xml.XmlTransformer
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -32,7 +32,7 @@ import java.text.SimpleDateFormat
 
 class IvyXmlModuleDescriptorWriterTest extends Specification {
 
-    private @Rule TemporaryFolder temporaryFolder;
+    private @Rule TestNameTestDirectoryProvider temporaryFolder;
     private ModuleDescriptor md = Mock();
     private ModuleRevisionId moduleRevisionId = Mock()
     private ModuleRevisionId resolvedModuleRevisionId = Mock()
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundExceptionTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundExceptionTest.groovy
new file mode 100644
index 0000000..2de2301
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundExceptionTest.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice
+
+import spock.lang.Specification
+
+import static org.apache.ivy.core.module.id.ModuleRevisionId.newInstance
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+class ModuleVersionNotFoundExceptionTest extends Specification {
+    def "formats message to include selector"() {
+        def exception1 = new ModuleVersionNotFoundException(newInstance("org", "a", "1.2"))
+        def exception2 = new ModuleVersionNotFoundException(newId("org", "a", "1.2"))
+        def exception3 = new ModuleVersionNotFoundException(newInstance("org", "a", "1.2"), "nothing matches %s")
+
+        expect:
+        exception1.message == 'Could not find org:a:1.2.'
+        exception2.message == 'Could not find org:a:1.2.'
+        exception3.message == 'nothing matches org:a:1.2'
+    }
+
+    def "can add incoming paths to exception"() {
+        def a = newInstance("org", "a", "1.2")
+        def b = newInstance("org", "b", "5")
+        def c = newInstance("org", "c", "1.0")
+
+        def exception = new ModuleVersionNotFoundException(newInstance("a", "b", "c"))
+        def onePath = exception.withIncomingPaths([[a, b, c]])
+
+        expect:
+        onePath.message == toPlatformLineSeparators('''Could not find a:b:c.
+Required by:
+    org:a:1.2 > org:b:5 > org:c:1.0''')
+        onePath.stackTrace == exception.stackTrace
+    }
+
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveExceptionTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveExceptionTest.groovy
index ad8a76f..6cbeda6 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveExceptionTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveExceptionTest.groovy
@@ -16,30 +16,46 @@
 package org.gradle.api.internal.artifacts.ivyservice
 
 import spock.lang.Specification
-import org.apache.ivy.core.module.id.ModuleRevisionId
-import static org.gradle.util.TextUtil.*
+
+import static org.apache.ivy.core.module.id.ModuleRevisionId.newInstance
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
 
 class ModuleVersionResolveExceptionTest extends Specification {
+    def "formats message to include selector"() {
+        def exception1 = new ModuleVersionResolveException(newInstance("org", "a", "1.2"), new RuntimeException())
+        def exception2 = new ModuleVersionResolveException(newSelector("org", "a", "1.2+"), "%s is broken")
+        def exception3 = new ModuleVersionResolveException(newId("org", "a", "1.2"), "%s is broken")
+        def exception4 = new ModuleVersionResolveException(newInstance("org", "a", "1.2"), "%s is broken")
+
+        expect:
+        exception1.message == 'Could not resolve org:a:1.2.'
+        exception2.message == 'org:a:1.2+ is broken'
+        exception3.message == 'org:a:1.2 is broken'
+        exception4.message == 'org:a:1.2 is broken'
+    }
+
     def "can add incoming paths to exception"() {
-        def a = ModuleRevisionId.newInstance("org", "a", "1.2")
-        def b = ModuleRevisionId.newInstance("org", "b", "5")
-        def c = ModuleRevisionId.newInstance("org", "c", "1.0")
+        def a = newInstance("org", "a", "1.2")
+        def b = newInstance("org", "b", "5")
+        def c = newInstance("org", "c", "1.0")
 
         def cause = new RuntimeException()
-        def exception = new ModuleVersionResolveException("broken", cause)
+        def exception = new ModuleVersionResolveException(newInstance("a", "b", "c"), cause)
         def onePath = exception.withIncomingPaths([[a, b, c]])
         def twoPaths = exception.withIncomingPaths([[a, b, c], [a, c]])
 
         expect:
-        exception.message == 'broken'
+        exception.message == 'Could not resolve a:b:c.'
 
-        onePath.message == toPlatformLineSeparators('''broken
+        onePath.message == toPlatformLineSeparators('''Could not resolve a:b:c.
 Required by:
     org:a:1.2 > org:b:5 > org:c:1.0''')
         onePath.stackTrace == exception.stackTrace
         onePath.cause == cause
 
-        twoPaths.message == toPlatformLineSeparators('''broken
+        twoPaths.message == toPlatformLineSeparators('''Could not resolve a:b:c.
 Required by:
     org:a:1.2 > org:b:5 > org:c:1.0
     org:a:1.2 > org:c:1.0''')
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedArtifactFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedArtifactFactoryTest.groovy
index 3385827..243015e 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedArtifactFactoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedArtifactFactoryTest.groovy
@@ -48,7 +48,7 @@ class ResolvedArtifactFactoryTest extends Specification {
         1 * lockingManager.useCache(!null, !null) >> {String displayName, Factory<?> action ->
             return action.create()
         }
-        1 * artifactResolver.resolve(artifact, _) >> { args -> args[1].resolved(file, null) }
+        1 * artifactResolver.resolve(artifact, _) >> { args -> args[1].resolved(file) }
         0 * _._
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolverSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolverSpec.groovy
new file mode 100644
index 0000000..660290e
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolverSpec.groovy
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor
+import org.apache.ivy.core.module.id.ModuleId
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.gradle.api.Action
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
+import spock.lang.Specification
+
+class VersionForcingDependencyToModuleResolverSpec extends Specification {
+    final target = Mock(DependencyToModuleVersionIdResolver)
+    final resolvedVersion = Mock(ModuleVersionIdResolveResult)
+    final forced = new ModuleRevisionId(new ModuleId('group', 'module'), 'forced')
+
+    def "passes through dependency when it does not match any rule"() {
+        def dep = dependency('org', 'module', '1.0')
+        def rule = Mock(Action)
+        def resolver = new VersionForcingDependencyToModuleResolver(target, rule)
+
+        when:
+        def result = resolver.resolve(dep)
+
+        then:
+        result == resolvedVersion
+
+        and:
+        1 * target.resolve(dep) >> resolvedVersion
+        1 * rule.execute( {it.requested.group == 'org' && it.requested.name == 'module' && it.requested.version == '1.0'} )
+        0 * target._
+    }
+
+    def "replaces dependency by rule"() {
+        def dep = dependency('org', 'module', '0.5')
+        def modified = dependency('org', 'module', '1.0')
+
+        def force = { it.useVersion("1.0") } as Action
+
+        def resolver = new VersionForcingDependencyToModuleResolver(target, force)
+
+        when:
+        SubstitutedModuleVersionIdResolveResult result = resolver.resolve(dep)
+
+        then:
+        result.result == resolvedVersion
+        result.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
+
+        and:
+        1 * dep.clone(new ModuleRevisionId(new ModuleId('org', 'module'), '1.0')) >> modified
+        1 * target.resolve(modified) >> resolvedVersion
+        0 * target._
+    }
+
+    def "explosive rule yields failure result that provides context"() {
+        def force = { throw new Error("Boo!") } as Action
+        def resolver = new VersionForcingDependencyToModuleResolver(target, force)
+
+        when:
+        def result = resolver.resolve(dependency('org', 'module', '0.5'))
+
+        then:
+        result.failure.message == "Could not resolve org:module:0.5."
+        result.failure.cause.message == 'Boo!'
+        result.selectionReason == VersionSelectionReasons.REQUESTED
+    }
+
+    def "failed result uses correct exception"() {
+        def force = { throw new Error("Boo!") } as Action
+        def resolver = new VersionForcingDependencyToModuleResolver(target, force)
+        def result = resolver.resolve(dependency('org', 'module', '0.5'))
+
+        when:
+        result.getId()
+
+        then:
+        def ex = thrown(ModuleVersionResolveException)
+        ex == result.failure
+
+        when:
+        result.resolve()
+
+        then:
+        def ex2 = thrown(ModuleVersionResolveException)
+        ex2 == result.failure
+    }
+
+    def dependency(String group, String module, String version) {
+        Mock(DependencyDescriptor) { getDependencyRevisionId() >> new ModuleRevisionId(new ModuleId(group, module), version) }
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolverTest.groovy
deleted file mode 100644
index 19ebe4d..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolverTest.groovy
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice
-
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor
-import org.apache.ivy.core.module.id.ModuleId
-import org.apache.ivy.core.module.id.ModuleRevisionId
-import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
-import spock.lang.Specification
-
-class VersionForcingDependencyToModuleResolverTest extends Specification {
-    final DependencyToModuleVersionIdResolver target = Mock()
-    final ModuleRevisionId forced = new ModuleRevisionId(new ModuleId('group', 'module'), 'forced')
-    final VersionForcingDependencyToModuleResolver resolver = new VersionForcingDependencyToModuleResolver(target, [new DefaultModuleVersionSelector('group', 'module', 'forced')])
-
-    def "passes through dependency when it does not match any forced group and module"() {
-        ModuleVersionIdResolveResult resolvedVersion = Mock()
-        def differentGroup = dependency('other', 'module')
-
-        when:
-        def result = resolver.resolve(differentGroup)
-
-        then:
-        result == resolvedVersion
-
-        and:
-        1 * target.resolve(differentGroup) >> resolvedVersion
-    }
-
-    def "replaces dependency when it matches a forced group and module"() {
-        ModuleVersionIdResolveResult resolvedVersion = Mock()
-        DependencyDescriptor modified = Mock()
-        def dep = dependency('group', 'module')
-
-        when:
-        ForcedModuleVersionIdResolveResult result = resolver.resolve(dep)
-
-        then:
-        result.result == resolvedVersion
-        result.selectionReason == ModuleVersionIdResolveResult.IdSelectionReason.forced
-
-        and:
-        1 * dep.clone(forced) >> modified
-        1 * target.resolve(modified) >> resolvedVersion
-    }
-
-    def dependency(String group, String module) {
-        DependencyDescriptor descriptor = Mock()
-        descriptor.dependencyId >> new ModuleId(group, module)
-        return descriptor
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolverTest.groovy
index 36d85e2..c448063 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolverTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolverTest.groovy
@@ -26,6 +26,8 @@ import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveExceptio
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ClientModuleDependencyDescriptor
 import spock.lang.Specification
 
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+
 /**
  * @author Hans Dockter
  */
@@ -74,7 +76,7 @@ class ClientModuleResolverTest extends Specification {
 
         then:
         1 * target.resolve(dependencyDescriptor, result)
-        _ * result.failure >> new ModuleVersionResolveException("broken")
+        _ * result.failure >> new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
         0 * result._
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepositoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepositoryTest.groovy
index 868a2fc..81d2d7a 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepositoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepositoryTest.groovy
@@ -25,7 +25,7 @@ import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePoli
 import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult
 import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ModuleResolutionCache
 import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleDescriptorCache
-import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex
+import org.gradle.api.internal.externalresource.cached.CachedArtifactIndex
 import org.gradle.api.internal.externalresource.ivy.ArtifactAtRepositoryKey
 import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData
@@ -38,12 +38,15 @@ class CachingModuleVersionRepositoryTest extends Specification {
     ModuleVersionRepository realRepo = Mock()
     ModuleResolutionCache moduleResolutionCache = Mock()
     ModuleDescriptorCache moduleDescriptorCache = Mock()
-    CachedExternalResourceIndex artifactAtRepositoryCache = Mock()
+    CachedArtifactIndex artifactAtRepositoryCache = Mock()
     CachePolicy cachePolicy = Mock()
-
     CachingModuleVersionRepository repo = new CachingModuleVersionRepository(realRepo, moduleResolutionCache, moduleDescriptorCache, artifactAtRepositoryCache, cachePolicy, new TrueTimeProvider())
-    
-    @Unroll "last modified date is cached - lastModified = #lastModified"(Date lastModified) {
+    ModuleRevisionId moduleRevisionId = Mock()
+    int descriptorHash = 1234
+    CachingModuleVersionRepository.CachingModuleSource moduleSource = Mock()
+
+    @Unroll
+    "last modified date is cached - lastModified = #lastModified"(Date lastModified) {
         given:
         ExternalResourceMetaData externalResourceMetaData = new DefaultExternalResourceMetaData("remote url", lastModified, -1, null, null)
         File file = new File("local")
@@ -51,22 +54,24 @@ class CachingModuleVersionRepositoryTest extends Specification {
         Artifact artifact = Mock()
         ArtifactRevisionId id = arid()
         ArtifactAtRepositoryKey atRepositoryKey = new ArtifactAtRepositoryKey(realRepo, id)
-        
 
         and:
+        _ * artifact.getModuleRevisionId() >> moduleRevisionId;
         _ * realRepo.isLocal() >> false
+        _ * moduleSource.descriptorHash >> descriptorHash
+        _ * moduleSource.isChangingModule >> true
         _ * artifactAtRepositoryCache.lookup(atRepositoryKey) >> null
-        _ * realRepo.resolve(artifact, result)
+        _ * realRepo.resolve(artifact, result, null)
         _ * result.file >> file
         _ * result.externalResourceMetaData >> externalResourceMetaData
         _ * artifact.getId() >> id
 
         when:
-        repo.resolve(artifact, result)
-        
+        repo.resolve(artifact, result, moduleSource)
+
         then:
-        1 * artifactAtRepositoryCache.store(atRepositoryKey, file, externalResourceMetaData)
-        
+        1 * artifactAtRepositoryCache.store(atRepositoryKey, file, descriptorHash)
+        0 * moduleDescriptorCache._
         where:
         lastModified << [new Date(), null]
     }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptorTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptorTest.groovy
index 70c6fba..a7d60ff 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptorTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptorTest.groovy
@@ -16,12 +16,16 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve
 
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor
+import org.apache.ivy.core.module.id.ModuleRevisionId
 import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
 import spock.lang.Specification
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
 
 class DefaultBuildableModuleVersionDescriptorTest extends Specification {
     final DefaultBuildableModuleVersionDescriptor descriptor = new DefaultBuildableModuleVersionDescriptor()
+    ModuleSource moduleSource = Mock()
 
     def "has unknown state by default"() {
         expect:
@@ -47,7 +51,7 @@ class DefaultBuildableModuleVersionDescriptorTest extends Specification {
     }
 
     def "can mark as failed"() {
-        def failure = new ModuleVersionResolveException("broken")
+        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
 
         when:
         descriptor.failed(failure)
@@ -59,15 +63,21 @@ class DefaultBuildableModuleVersionDescriptorTest extends Specification {
 
     def "can mark as resolved"() {
         def moduleDescriptor = Mock(ModuleDescriptor)
+        ModuleRevisionId moduleRevisionId = Mock();
+        1 * moduleRevisionId.organisation >> "group"
+        1 * moduleRevisionId.name >> "project"
+        1 * moduleRevisionId.revision >> "1.0"
+        1 * moduleDescriptor.moduleRevisionId >> moduleRevisionId
 
         when:
-        descriptor.resolved(moduleDescriptor, true)
+        descriptor.resolved(moduleDescriptor, true, moduleSource)
 
         then:
         descriptor.state == BuildableModuleVersionDescriptor.State.Resolved
         descriptor.failure == null
         descriptor.descriptor == moduleDescriptor
         descriptor.changing
+        descriptor.moduleSource == moduleSource
     }
 
     def "cannot get result when not resolved"() {
@@ -86,7 +96,7 @@ class DefaultBuildableModuleVersionDescriptorTest extends Specification {
 
     def "cannot get result when failed"() {
         given:
-        def failure = new ModuleVersionResolveException("broken")
+        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
         descriptor.failed(failure)
 
         when:
@@ -118,4 +128,39 @@ class DefaultBuildableModuleVersionDescriptorTest extends Specification {
         then:
         thrown(IllegalStateException)
     }
+
+    def "cannot get ModuleSource when failed"() {
+        given:
+        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
+        descriptor.failed(failure)
+
+        when:
+        descriptor.getModuleSource()
+
+        then:
+        ModuleVersionResolveException e = thrown()
+        e == failure
+    }
+
+    def "cannot get ModuleSource when missing"() {
+        given:
+        descriptor.missing()
+
+        when:
+        descriptor.getModuleSource()
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot get ModuleSource when probably missing"() {
+        given:
+        descriptor.probablyMissing()
+
+        when:
+        descriptor.getModuleSource()
+
+        then:
+        thrown(IllegalStateException)
+    }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolverTest.groovy
index aee0737..4a779fc 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolverTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolverTest.groovy
@@ -18,11 +18,16 @@ package org.gradle.api.internal.artifacts.ivyservice.ivyresolve
 import org.apache.ivy.core.module.descriptor.Artifact
 import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
 import org.apache.ivy.core.module.descriptor.DependencyDescriptor
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor
 import org.apache.ivy.core.module.id.ModuleRevisionId
 import org.apache.ivy.plugins.version.VersionMatcher
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
 import org.gradle.api.internal.artifacts.ivyservice.*
 import spock.lang.Specification
 
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
+
 class LazyDependencyToModuleResolverTest extends Specification {
     final DependencyToModuleResolver target = Mock()
     final VersionMatcher matcher = Mock()
@@ -36,7 +41,9 @@ class LazyDependencyToModuleResolverTest extends Specification {
         def idResolveResult = resolver.resolve(dependency)
 
         then:
-        idResolveResult.id == dependency.dependencyRevisionId
+        idResolveResult.id.group == module.moduleRevisionId.organisation
+        idResolveResult.id.name == module.moduleRevisionId.name
+        idResolveResult.id.version == module.moduleRevisionId.revision
 
         and:
         0 * target._
@@ -45,17 +52,19 @@ class LazyDependencyToModuleResolverTest extends Specification {
         def moduleResolveResult = idResolveResult.resolve()
 
         then:
-        moduleResolveResult.id == module.moduleRevisionId
+        moduleResolveResult.id.group == module.moduleRevisionId.organisation
+        moduleResolveResult.id.name == module.moduleRevisionId.name
+        moduleResolveResult.id.version == module.moduleRevisionId.revision
+
         moduleResolveResult.descriptor == module
 
-        1 * target.resolve(dependency, _) >> { args -> args[1].resolved(module.moduleRevisionId, module, Mock(ArtifactResolver))}
+        1 * target.resolve(dependency, _) >> { args -> args[1].resolved(moduleIdentifier(module), module, Mock(ArtifactResolver))}
         0 * target._
     }
 
     def "resolves module for dynamic version dependency immediately"() {
         def dependency = dependency()
         def module = module()
-
         given:
         matcher.isDynamic(_) >> true
 
@@ -64,10 +73,12 @@ class LazyDependencyToModuleResolverTest extends Specification {
         def id = idResolveResult.id
 
         then:
-        id == module.moduleRevisionId
+        id.group == module.moduleRevisionId.organisation
+        id.name == module.moduleRevisionId.name
+        id.version == module.moduleRevisionId.revision
 
         and:
-        1 * target.resolve(dependency, _) >> { args -> args[1].resolved(module.moduleRevisionId, module, Mock(ArtifactResolver))}
+        1 * target.resolve(dependency, _) >> { args -> args[1].resolved(moduleIdentifier(module), module, Mock(ArtifactResolver))}
         0 * target._
 
         when:
@@ -77,6 +88,10 @@ class LazyDependencyToModuleResolverTest extends Specification {
         0 * target._
     }
 
+    def moduleIdentifier(ModuleDescriptor moduleDescriptor) {
+        return new DefaultModuleVersionIdentifier(moduleDescriptor.moduleRevisionId.organisation, moduleDescriptor.moduleRevisionId.name, moduleDescriptor.moduleRevisionId.revision)
+    }
+
     def "does not resolve module more than once"() {
         def dependency = dependency()
         def module = module()
@@ -98,7 +113,7 @@ class LazyDependencyToModuleResolverTest extends Specification {
 
     def "collects failure to resolve module"() {
         def dependency = dependency()
-        def failure = new ModuleVersionResolveException("broken")
+        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
 
         when:
         def idFailureResult = resolver.resolve(dependency)
@@ -138,10 +153,10 @@ class LazyDependencyToModuleResolverTest extends Specification {
 
         then:
         resolveResult.failure instanceof ModuleVersionNotFoundException
-        resolveResult.failure.message == "Could not find group:group, module:module, version:1.0."
+        resolveResult.failure.message == "Could not find group:module:1.0."
 
         and:
-        1 * target.resolve(dependency, _) >> { args -> args[1].failed(new ModuleVersionNotFoundException("broken"))}
+        1 * target.resolve(dependency, _) >> { args -> args[1].failed(new ModuleVersionNotFoundException(newId("org", "a", "1.2")))}
     }
 
     def "collects and wraps unexpected module resolve failure"() {
@@ -153,7 +168,7 @@ class LazyDependencyToModuleResolverTest extends Specification {
 
         then:
         resolveResult.failure instanceof ModuleVersionResolveException
-        resolveResult.failure.message == "Could not resolve group:group, module:module, version:1.0."
+        resolveResult.failure.message == "Could not resolve group:module:1.0."
 
         and:
         1 * target.resolve(dependency, _) >> { throw failure }
@@ -170,10 +185,10 @@ class LazyDependencyToModuleResolverTest extends Specification {
 
         then:
         idResolveResult.failure instanceof ModuleVersionNotFoundException
-        idResolveResult.failure.message == "Could not find any version that matches group:group, module:module, version:1.0."
+        idResolveResult.failure.message == "Could not find any version that matches group:module:1.0."
 
         and:
-        1 * target.resolve(dependency, _) >> { args -> args[1].failed(new ModuleVersionNotFoundException("missing"))}
+        1 * target.resolve(dependency, _) >> { args -> args[1].failed(new ModuleVersionNotFoundException(newId("org", "a", "1.2")))}
 
         when:
         idResolveResult.id
@@ -205,7 +220,7 @@ class LazyDependencyToModuleResolverTest extends Specification {
         def resolveResult = resolver.resolve(dependency).resolve()
 
         then:
-        1 * target.resolve(dependency, _) >> { args -> args[1].resolved(module.moduleRevisionId, module, targetResolver)}
+        1 * target.resolve(dependency, _) >> { args -> args[1].resolved(moduleIdentifier(module), module, targetResolver)}
 
         when:
         resolveResult.artifactResolver.resolve(artifact, result)
@@ -220,6 +235,7 @@ class LazyDependencyToModuleResolverTest extends Specification {
         def dependency = dependency()
         def artifact = artifact()
         def module = module()
+
         ArtifactResolver targetResolver = Mock()
         BuildableArtifactResolveResult result = Mock()
         def failure = new RuntimeException("broken")
@@ -228,7 +244,7 @@ class LazyDependencyToModuleResolverTest extends Specification {
         def resolveResult = resolver.resolve(dependency).resolve()
 
         then:
-        1 * target.resolve(dependency, _) >> { args -> args[1].resolved(module.moduleRevisionId, module, targetResolver)}
+        1 * target.resolve(dependency, _) >> { args -> args[1].resolved(moduleIdentifier(module), module, targetResolver)}
 
         when:
         resolveResult.artifactResolver.resolve(artifact, result)
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChainTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChainTest.groovy
index a5f07f8..c2e5f6d 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChainTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChainTest.groovy
@@ -16,25 +16,37 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve
 
-import spock.lang.Specification
 import org.apache.ivy.core.module.descriptor.DependencyDescriptor
-import org.gradle.api.internal.artifacts.ivyservice.BuildableModuleVersionResolveResult
-import org.apache.ivy.plugins.resolver.ResolverSettings
-import org.apache.ivy.core.module.id.ModuleRevisionId
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor
-import org.apache.ivy.plugins.version.VersionMatcher
+import org.apache.ivy.core.module.id.ModuleRevisionId
 import org.apache.ivy.plugins.latest.LatestRevisionStrategy
+import org.apache.ivy.plugins.resolver.ResolverSettings
+import org.apache.ivy.plugins.version.VersionMatcher
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.ivyservice.BuildableModuleVersionResolveResult
+import spock.lang.Specification
 
 class UserResolverChainTest extends Specification {
     final UserResolverChain resolver = new UserResolverChain()
     final ModuleRevisionId dependencyId = Stub()
     final DependencyDescriptor dependency = Stub()
     final ModuleDescriptor descriptor = descriptor("1.2")
-    final ModuleRevisionId resolvedId = descriptor.resolvedModuleRevisionId
+    final ModuleVersionIdentifier resolvedId = moduleVersionIdentifier(descriptor)
+
+    ModuleVersionIdentifier moduleVersionIdentifier(ModuleDescriptor moduleDescriptor) {
+        def moduleRevId = moduleDescriptor.moduleRevisionId
+        new DefaultModuleVersionIdentifier(moduleRevId.organisation, moduleRevId.name, moduleRevId.revision)
+    }
+
     final BuildableModuleVersionResolveResult result = Mock()
     final VersionMatcher matcher = Stub()
+    final ModuleSource moduleSource = Mock()
 
     def setup() {
+        _ * dependencyId.organisation >> "group"
+        _ * dependencyId.name >> "project"
+        _ * dependencyId.revision >> "1.0"
         dependency.dependencyRevisionId >> dependencyId
         def settings = Stub(ResolverSettings)
         _ * settings.versionMatcher >> matcher
@@ -52,9 +64,12 @@ class UserResolverChainTest extends Specification {
 
         then:
         1 * repo.getLocalDependency(dependency, _) >> { dep, result ->
-            result.resolved(descriptor, true)
+            result.resolved(descriptor, true, moduleSource)
+        }
+        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, rep ->
+            assert rep.delegate == repo
+            assert rep.moduleSource == moduleSource
         }
-        1 * result.resolved(resolvedId, descriptor, repo)
 
         and:
         _ * repo.name >> "repo"
@@ -73,10 +88,12 @@ class UserResolverChainTest extends Specification {
         then:
         1 * repo.getLocalDependency(dependency, _)
         1 * repo.getDependency(dependency, _) >> { dep, result ->
-            result.resolved(descriptor, true)
+            result.resolved(descriptor, true, moduleSource)
+        }
+        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, rep ->
+            assert rep.delegate == repo
+            assert rep.moduleSource == moduleSource
         }
-        1 * result.resolved(resolvedId, descriptor, repo)
-
         and:
         _ * repo.name >> "repo"
         0 * repo._
@@ -96,9 +113,12 @@ class UserResolverChainTest extends Specification {
             result.probablyMissing()
         }
         1 * repo.getDependency(dependency, _) >> { dep, result ->
-            result.resolved(descriptor, true)
+            result.resolved(descriptor, true, moduleSource)
+        }
+        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, rep ->
+            assert rep.delegate == repo
+            assert rep.moduleSource == moduleSource
         }
-        1 * result.resolved(resolvedId, descriptor, repo)
 
         and:
         _ * repo.name >> "repo"
@@ -118,7 +138,12 @@ class UserResolverChainTest extends Specification {
         1 * repo.getLocalDependency(dependency, _) >> { dep, result ->
             result.missing()
         }
-        1 * result.notFound(dependencyId)
+        1 * result.notFound(_ as ModuleVersionIdentifier) >> { ModuleVersionIdentifier mdV ->
+            assert mdV.group == "group"
+            assert mdV.name == "project"
+            assert mdV.version == "1.0"
+
+        }
 
         and:
         _ * repo.name >> "repo"
@@ -141,7 +166,11 @@ class UserResolverChainTest extends Specification {
         1 * repo.getDependency(dependency, _) >> { dep, result ->
             result.missing()
         }
-        1 * result.notFound(dependencyId)
+        1 * result.notFound(_ as ModuleVersionIdentifier) >> { ModuleVersionIdentifier moduleVersionIdentifier ->
+            assert moduleVersionIdentifier.group == "group"
+            assert moduleVersionIdentifier.name == "project"
+            assert moduleVersionIdentifier.version == "1.0"
+        }
 
         and:
         _ * repo.name >> "repo"
@@ -165,15 +194,18 @@ class UserResolverChainTest extends Specification {
 
         then:
         1 * repo1.getLocalDependency(dependency, _) >> { dep, result ->
-            result.resolve(descriptor("1.1"), true)
+            result.resolve(descriptor("1.1"), null)
         }
         1 * repo2.getLocalDependency(dependency, _) >> { dep, result ->
-            result.resolved(version2, true)
+            result.resolved(version2, true, moduleSource)
         }
         1 * repo3.getLocalDependency(dependency, _) >> { dep, result ->
-            result.resolved(descriptor("1.0"), true)
+            result.resolved(descriptor("1.0"), true, null)
+        }
+        1 * result.resolved(moduleVersionIdentifier(version2), version2, _) >> { revision, descriptor, repo ->
+            assert repo.delegate == repo2
+            assert repo.moduleSource == moduleSource
         }
-        1 * result.resolved(version2.resolvedModuleRevisionId, version2, repo2)
 
         and:
         _ * repo1.name >> "repo1"
@@ -200,9 +232,12 @@ class UserResolverChainTest extends Specification {
 
         then:
         1 * repo1.getLocalDependency(dependency, _) >> { dep, result ->
-            result.resolved(descriptor, true)
+            result.resolved(descriptor, true, moduleSource)
+        }
+        1 * result.resolved(resolvedId, descriptor, _) >> { revisionId, descriptor, repo ->
+            assert repo.delegate == repo1
+            assert repo.moduleSource == moduleSource
         }
-        1 * result.resolved(resolvedId, descriptor, repo1)
 
         and:
         _ * repo1.name >> "repo1"
@@ -229,9 +264,12 @@ class UserResolverChainTest extends Specification {
             result.missing()
         }
         1 * repo2.getLocalDependency(dependency, _) >> { dep, result ->
-            result.resolved(descriptor, true)
+            result.resolved(descriptor, true, moduleSource)
+        }
+        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, repo ->
+            assert repo.delegate == repo2
+            assert repo.moduleSource == moduleSource
         }
-        1 * result.resolved(resolvedId, descriptor, repo2)
 
         and:
         _ * repo1.name >> "repo"
@@ -256,10 +294,12 @@ class UserResolverChainTest extends Specification {
             result.probablyMissing()
         }
         1 * repo2.getLocalDependency(dependency, _) >> { dep, result ->
-            result.resolved(descriptor, true)
+            result.resolved(descriptor, true, moduleSource)
+        }
+        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, repo ->
+            assert repo.delegate == repo2
+            assert repo.moduleSource == moduleSource
         }
-        1 * result.resolved(resolvedId, descriptor, repo2)
-
         and:
         _ * repo1.name >> "repo"
         _ * repo2.name >> "repo"
@@ -284,10 +324,12 @@ class UserResolverChainTest extends Specification {
         }
         1 * repo2.getLocalDependency(dependency, _)
         1 * repo2.getDependency(dependency, _) >> { dep, result ->
-            result.resolved(descriptor, true)
+            result.resolved(descriptor, true, moduleSource)
+        }
+        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, repo ->
+            assert repo.delegate == repo2
+            assert repo.moduleSource == moduleSource
         }
-        1 * result.resolved(resolvedId, descriptor, repo2)
-
         and:
         _ * repo1.name >> "repo"
         _ * repo2.name >> "repo"
@@ -317,10 +359,12 @@ class UserResolverChainTest extends Specification {
             result.missing()
         }
         1 * repo2.getDependency(dependency, _) >> { dep, result ->
-            result.resolved(descriptor, true)
+            result.resolved(descriptor, true, moduleSource)
+        }
+        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, repo ->
+            assert repo.delegate == repo2
+            assert repo.moduleSource == moduleSource
         }
-        1 * result.resolved(resolvedId, descriptor, repo2)
-
         and:
         _ * repo1.name >> "repo"
         _ * repo2.name >> "repo"
@@ -347,9 +391,12 @@ class UserResolverChainTest extends Specification {
             result.probablyMissing()
         }
         1 * repo2.getDependency(dependency, _) >> { dep, result ->
-            result.resolved(descriptor, true)
+            result.resolved(descriptor, true, moduleSource)
+        }
+        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, repo ->
+            assert repo.delegate == repo2
+            assert repo.moduleSource == moduleSource
         }
-        1 * result.resolved(resolvedId, descriptor, repo2)
 
         and:
         _ * repo1.name >> "repo"
@@ -378,10 +425,12 @@ class UserResolverChainTest extends Specification {
             result.missing()
         }
         1 * repo1.getDependency(dependency, _) >> { dep, result ->
-            result.resolved(descriptor, true)
+            result.resolved(descriptor, true, moduleSource)
+        }
+        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, repo ->
+            assert repo.delegate == repo1
+            assert repo.moduleSource == moduleSource
         }
-        1 * result.resolved(resolvedId, descriptor, repo1)
-
         and:
         _ * repo1.name >> "repo"
         _ * repo2.name >> "repo"
@@ -405,9 +454,12 @@ class UserResolverChainTest extends Specification {
             throw new RuntimeException("broken")
         }
         1 * repo2.getLocalDependency(dependency, _) >> { dep, result ->
-            result.resolved(descriptor, true)
+            result.resolved(descriptor, true, moduleSource)
+        }
+        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, repo ->
+            assert repo.delegate == repo2
+            assert repo.moduleSource == moduleSource
         }
-        1 * result.resolved(resolvedId, descriptor, repo2)
 
         and:
         _ * repo1.name >> "repo"
@@ -434,10 +486,12 @@ class UserResolverChainTest extends Specification {
         }
         1 * repo2.getLocalDependency(dependency, _)
         1 * repo2.getDependency(dependency, _) >> { dep, result ->
-            result.resolved(descriptor, true)
+            result.resolved(descriptor, true, moduleSource)
+        }
+        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, repo ->
+            assert repo.delegate == repo2
+            assert repo.moduleSource == moduleSource
         }
-        1 * result.resolved(resolvedId, descriptor, repo2)
-
         and:
         _ * repo1.name >> "repo"
         _ * repo2.name >> "repo"
@@ -465,7 +519,7 @@ class UserResolverChainTest extends Specification {
         1 * repo2.getDependency(dependency, _) >> { dep, result ->
             result.missing()
         }
-        1 * result.failed({it.cause == failure})
+        1 * result.failed({ it.cause == failure })
 
         and:
         _ * repo1.name >> "repo"
@@ -495,7 +549,7 @@ class UserResolverChainTest extends Specification {
         1 * repo2.getDependency(dependency, _) >> { dep, result ->
             result.missing()
         }
-        1 * result.failed({it.cause == failure})
+        1 * result.failed({ it.cause == failure })
 
         and:
         _ * repo1.name >> "repo"
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParserTest.groovy
index ba2682c..a7162e4 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParserTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParserTest.groovy
@@ -16,15 +16,15 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser
 
-import spock.lang.Specification
+import org.apache.ivy.core.settings.IvySettings
 import org.apache.ivy.plugins.parser.ParserSettings
 import org.apache.ivy.plugins.repository.Resource
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
-import org.gradle.util.TemporaryFolder
-import org.apache.ivy.core.settings.IvySettings
+import spock.lang.Specification
 
 class DownloadedIvyModuleDescriptorParserTest extends Specification {
-    @Rule TemporaryFolder tmpDir
+    @Rule TestNameTestDirectoryProvider tmpDir
     final DownloadedIvyModuleDescriptorParser parser = new DownloadedIvyModuleDescriptorParser()
 
     def "discards the default attribute"() {
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserTest.groovy
index ff07174..57a5d3a 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserTest.groovy
@@ -20,16 +20,16 @@ import org.apache.ivy.core.module.descriptor.ModuleDescriptor
 import org.apache.ivy.core.module.id.ArtifactRevisionId
 import org.apache.ivy.core.module.id.ModuleRevisionId
 import org.apache.ivy.plugins.parser.ParserSettings
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
-import spock.lang.Specification
 import spock.lang.Issue
+import spock.lang.Specification
 
 class GradlePomModuleDescriptorParserTest extends Specification {
-    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final GradlePomModuleDescriptorParser parser = new GradlePomModuleDescriptorParser()
-    final ParserSettings ivySettings = Mock()
+    final ModuleScopedParserSettings ivySettings = Mock()
     TestFile pomFile
 
     def "setup"() {
@@ -37,7 +37,7 @@ class GradlePomModuleDescriptorParserTest extends Specification {
     }
 
     def "parses simple pom"() {
-        when:
+        given:
         pomFile << """
 <project>
     <modelVersion>4.0.0</modelVersion>
@@ -57,6 +57,9 @@ class GradlePomModuleDescriptorParserTest extends Specification {
 </project>
 """
         and:
+        ivySettings.currentRevisionId >> moduleId('group-one', 'artifact-one', 'version-one')
+
+        when:
         def descriptor = parsePom()
 
         then:
@@ -68,7 +71,7 @@ class GradlePomModuleDescriptorParserTest extends Specification {
     }
 
     def "pom with dependency with classifier"() {
-        when:
+        given:
         pomFile << """
 <project>
     <modelVersion>4.0.0</modelVersion>
@@ -87,6 +90,9 @@ class GradlePomModuleDescriptorParserTest extends Specification {
 </project>
 """
         and:
+        ivySettings.currentRevisionId >> moduleId('group-one', 'artifact-one', 'version-one')
+
+        when:
         def descriptor = parsePom()
 
         then:
@@ -99,7 +105,7 @@ class GradlePomModuleDescriptorParserTest extends Specification {
 
     @Issue("GRADLE-2068")
     def "pom with dependency with empty classifier is treated like dependency without classifier"() {
-        when:
+        given:
         pomFile << """
 <project>
     <modelVersion>4.0.0</modelVersion>
@@ -118,6 +124,9 @@ class GradlePomModuleDescriptorParserTest extends Specification {
 </project>
 """
         and:
+        ivySettings.currentRevisionId >> moduleId('group-one', 'artifact-one', 'version-one')
+
+        when:
         def descriptor = parsePom()
 
         then:
@@ -130,7 +139,7 @@ class GradlePomModuleDescriptorParserTest extends Specification {
 
     @Issue("GRADLE-2076")
     def "pom with packaging of type eclipse-plugin creates jar artifact"() {
-        when:
+        given:
         pomFile << """
 <project>
     <modelVersion>4.0.0</modelVersion>
@@ -141,6 +150,9 @@ class GradlePomModuleDescriptorParserTest extends Specification {
 </project>
 """
         and:
+        ivySettings.currentRevisionId >> moduleId('group-one', 'artifact-one', 'version-one')
+
+        when:
         def descriptor = parsePom()
 
         then:
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParserTest.groovy
new file mode 100644
index 0000000..1c41312
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParserTest.groovy
@@ -0,0 +1,412 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser
+
+import org.apache.ivy.core.module.descriptor.*
+import org.apache.ivy.core.module.id.ModuleId
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.apache.ivy.core.settings.IvySettings
+import org.apache.ivy.plugins.conflict.ConflictManager
+import org.apache.ivy.plugins.conflict.FixedConflictManager
+import org.apache.ivy.plugins.conflict.NoConflictManager
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher
+import org.apache.ivy.plugins.matcher.GlobPatternMatcher
+import org.apache.ivy.plugins.matcher.PatternMatcher
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.Resources
+import org.hamcrest.Matchers
+import org.junit.Rule
+import spock.lang.Specification
+
+import java.text.ParseException
+
+import static junit.framework.Assert.*
+import static org.junit.Assert.assertThat
+
+class IvyXmlModuleDescriptorParserTest extends Specification {
+    @Rule public final Resources resources = new Resources()
+    @Rule TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
+
+    IvyXmlModuleDescriptorParser parser = new IvyXmlModuleDescriptorParser()
+    IvySettings settings = new IvySettings()
+
+    def setup() {
+        settings.setDefaultCache(temporaryFolder.createDir("ivy/cache"))
+    }
+
+    def testEmptyDependencies() throws Exception {
+        when:
+        ModuleDescriptor md = parser.parseDescriptor(settings, resources.getResource("test-empty-dependencies.xml").toURI().toURL(), true)
+        then:
+        md != null
+        "myorg" == md.getModuleRevisionId().getOrganisation()
+        "mymodule" == md.getModuleRevisionId().getName()
+        "myrev" == md.getModuleRevisionId().getRevision()
+        "integration" == md.getStatus()
+        md.getConfigurations() != null
+        Arrays.asList(new Configuration("default")) == Arrays.asList(md.getConfigurations())
+        md.getArtifacts("default") != null
+        1 == md.getArtifacts("default").length
+        "mymodule" == md.getArtifacts("default")[0].getName()
+        "jar" == md.getArtifacts("default")[0].getType()
+        md.getDependencies() != null
+        0 == md.getDependencies().length
+    }
+
+    public void testBadConfs() throws IOException {
+        when:
+        parser.parseDescriptor(settings, resources.getResource("test-bad-confs.xml").toURI().toURL(), true)
+        then:
+        def e = thrown(ParseException)
+        assertThat(e.message, Matchers.startsWith("unknown configuration 'invalidConf'"))
+    }
+
+    public void testCyclicConfs() throws IOException {
+        when:
+        parser.parseDescriptor(settings, resources.getResource("test-cyclic-confs1.xml").toURI().toURL(), true)
+        then:
+        def e = thrown(ParseException)
+        assertThat(e.message, Matchers.startsWith("illegal cycle detected in configuration extension: A => B => A"))
+    }
+
+    public void testFull() throws Exception {
+        when:
+        ModuleDescriptor md = parser.parseDescriptor(settings, resources.getResource("test-full.xml").toURI().toURL(), false)
+        then:
+        assertNotNull(md)
+        assertEquals("myorg", md.getModuleRevisionId().getOrganisation())
+        assertEquals("mymodule", md.getModuleRevisionId().getName())
+        assertEquals("myrev", md.getModuleRevisionId().getRevision())
+        assertEquals("integration", md.getStatus())
+        Date pubdate = new GregorianCalendar(2004, 10, 1, 11, 0, 0).getTime()
+        assertEquals(pubdate, md.getPublicationDate())
+
+        License[] licenses = md.getLicenses()
+        assertEquals(1, licenses.length)
+        assertEquals("MyLicense", licenses[0].getName())
+        assertEquals("http://www.my.org/mymodule/mylicense.html", licenses[0].getUrl())
+
+        assertEquals("http://www.my.org/mymodule/", md.getHomePage())
+        assertEquals("This module is <b>great</b> !<br/>\n\t"
+                + "You can use it especially with myconf1 and myconf2, "
+                + "and myconf4 is not too bad too.",
+                md.getDescription().replaceAll("\r\n", "\n").replace('\r', '\n'))
+
+        assertEquals(1, md.getExtraInfo().size())
+        assertEquals("56576", md.getExtraInfo().get("e:someExtra"))
+
+        Configuration[] confs = md.getConfigurations()
+        assertNotNull(confs)
+        assertEquals(5, confs.length)
+
+        assertConf(md, "myconf1", "desc 1", Configuration.Visibility.PUBLIC, new String[0])
+        assertConf(md, "myconf2", "desc 2", Configuration.Visibility.PUBLIC, new String[0])
+        assertConf(md, "myconf3", "desc 3", Configuration.Visibility.PRIVATE, new String[0])
+        assertConf(md, "myconf4", "desc 4", Configuration.Visibility.PUBLIC, ["myconf1", "myconf2"].toArray(new String[2]))
+        assertConf(md, "myoldconf", "my old desc", Configuration.Visibility.PUBLIC, new String[0])
+
+        assertArtifacts(md.getArtifacts("myconf1"), ["myartifact1", "myartifact2", "myartifact3", "myartifact4"].toArray(new String[4]))
+
+        assertArtifacts(md.getArtifacts("myconf2"), ["myartifact1", "myartifact3"].toArray(new String[2]))
+        assertArtifacts(md.getArtifacts("myconf3"), ["myartifact1", "myartifact3", "myartifact4"].toArray(new String[3]))
+        assertArtifacts(md.getArtifacts("myconf4"), ["myartifact1"].toArray(new String[1]))
+
+        DependencyDescriptor[] dependencies = md.getDependencies()
+        assertNotNull(dependencies)
+        assertEquals(13, dependencies.length)
+
+        verifyFullDependencies(dependencies)
+        verifyConflictManagers(md)
+
+        assertEquals(ModuleRevisionId.parse("yourorg#yourmodule1#BRANCH;1.0"),
+                md.mediate(new DefaultDependencyDescriptor(
+                        ModuleRevisionId.parse("yourorg#yourmodule1;2.0"), false))
+                        .getDependencyRevisionId())
+
+        ExcludeRule[] rules = md.getAllExcludeRules()
+        assertNotNull(rules)
+        assertEquals(2, rules.length)
+        assertEquals(GlobPatternMatcher.INSTANCE, rules[0].getMatcher())
+        assertEquals(ExactPatternMatcher.INSTANCE, rules[1].getMatcher())
+        assertEquals(Arrays.asList("myconf1"), Arrays.asList(rules[0]
+                .getConfigurations()))
+        assertEquals(Arrays.asList("myconf1", "myconf2", "myconf3", "myconf4", "myoldconf"), Arrays.asList(rules[1].getConfigurations()))
+        true
+    }
+
+    def verifyConflictManagers(ModuleDescriptor md) {
+        ConflictManager cm = md.getConflictManager(new ModuleId("yourorg", "yourmodule1"))
+        assertNotNull(cm)
+        assertTrue(cm instanceof NoConflictManager)
+
+        cm = md.getConflictManager(new ModuleId("yourorg", "yourmodule2"))
+        assertNotNull(cm)
+        assertTrue(cm instanceof NoConflictManager)
+
+        cm = md.getConflictManager(new ModuleId("theirorg", "theirmodule1"))
+        assertNotNull(cm)
+        assertTrue(cm instanceof FixedConflictManager)
+        FixedConflictManager fcm = (FixedConflictManager) cm
+        assertEquals(2, fcm.getRevs().size())
+        assertTrue(fcm.getRevs().contains("1.0"))
+        assertTrue(fcm.getRevs().contains("1.1"))
+
+        cm = md.getConflictManager(new ModuleId("theirorg", "theirmodule2"))
+        assertNull(cm)
+        true
+    }
+
+    def verifyFullDependencies(DependencyDescriptor[] dependencies) {
+        // no conf def => equivalent to *->*
+        DependencyDescriptor dd = getDependency(dependencies, "mymodule2")
+        assertNotNull(dd)
+        assertEquals("myorg", dd.getDependencyId().getOrganisation())
+        assertEquals("2.0", dd.getDependencyRevisionId().getRevision())
+        assertEquals(["*"], Arrays.asList(dd.getModuleConfigurations()))
+        assertEquals(["*"], Arrays.asList(dd.getDependencyConfigurations("myconf1")))
+        assertEquals(["*"], Arrays.asList(dd.getDependencyConfigurations(["myconf2", "myconf3", "myconf4"].toArray(new String[3]))))
+        assertDependencyArtifactIncludeRules(dd, ["myconf1", "myconf2", "myconf3", "myconf4"], [])
+        assertFalse(dd.isChanging())
+        assertTrue(dd.isTransitive())
+
+        // changing = true
+        dd = getDependency(dependencies, "mymodule3")
+        assertNotNull(dd)
+        assertTrue(getDependency(dependencies, "mymodule3").isChanging())
+        assertFalse(getDependency(dependencies, "mymodule3").isTransitive())
+        // conf="myconf1" => equivalent to myconf1->myconf1
+        dd = getDependency(dependencies, "yourmodule1")
+        assertNotNull(dd)
+        assertEquals("yourorg", dd.getDependencyId().getOrganisation())
+        assertEquals("trunk", dd.getDependencyRevisionId().getBranch())
+        assertEquals("1.1", dd.getDependencyRevisionId().getRevision())
+        assertEquals("branch1", dd.getDynamicConstraintDependencyRevisionId().getBranch())
+        assertEquals("1+", dd.getDynamicConstraintDependencyRevisionId().getRevision())
+        assertEquals("yourorg#yourmodule1#branch1;1+", dd.getDynamicConstraintDependencyRevisionId().toString())
+
+        assertEquals(["myconf1"], Arrays.asList(dd.getModuleConfigurations()))
+        assertEquals(["myconf1"], Arrays.asList(dd.getDependencyConfigurations("myconf1")))
+        assertEquals([], Arrays.asList(dd.getDependencyConfigurations(["myconf2", "myconf3", "myconf4"].toArray(new String[3]))))
+        assertDependencyArtifactIncludeRules(dd, ["myconf1", "myconf2", "myconf3", "myconf4"], [])
+
+        // conf="myconf1->yourconf1"
+        dd = getDependency(dependencies, "yourmodule2")
+        assertNotNull(dd)
+        assertEquals("yourorg", dd.getDependencyId().getOrganisation())
+        assertEquals("2+", dd.getDependencyRevisionId().getRevision())
+        assertEquals(["myconf1"], Arrays.asList(dd.getModuleConfigurations()))
+        assertEquals(["yourconf1"], Arrays.asList(dd.getDependencyConfigurations("myconf1")))
+        assertEquals([], Arrays.asList(dd.getDependencyConfigurations(["myconf2", "myconf3", "myconf4"].toArray(new String[3]))))
+        assertDependencyArtifactIncludeRules(dd, ["myconf1", "myconf2", "myconf3", "myconf4"], [])
+
+        // conf="myconf1->yourconf1, yourconf2"
+        dd = getDependency(dependencies, "yourmodule3")
+        assertNotNull(dd)
+        assertEquals("yourorg", dd.getDependencyId().getOrganisation())
+        assertEquals("3.1", dd.getDependencyRevisionId().getRevision())
+        assertEquals(["myconf1"], Arrays.asList(dd.getModuleConfigurations()))
+        assertEquals(["yourconf1", "yourconf2"], Arrays.asList(dd.getDependencyConfigurations("myconf1")))
+        assertEquals([], Arrays.asList(dd.getDependencyConfigurations(["myconf2", "myconf3", "myconf4"].toArray(new String[3]))))
+        assertDependencyArtifactIncludeRules(dd, ["myconf1", "myconf2", "myconf3", "myconf4"], [])
+
+        // conf="myconf1, myconf2->yourconf1, yourconf2"
+        dd = getDependency(dependencies, "yourmodule4")
+        assertNotNull(dd)
+        assertEquals("yourorg", dd.getDependencyId().getOrganisation())
+        assertEquals("4.1", dd.getDependencyRevisionId().getRevision())
+        assertEquals(new HashSet(["myconf1", "myconf2"]), new HashSet(
+                Arrays.asList(dd.getModuleConfigurations())))
+        assertEquals(["yourconf1", "yourconf2"], Arrays.asList(dd
+                .getDependencyConfigurations("myconf1")))
+        assertEquals(["yourconf1", "yourconf2"], Arrays.asList(dd
+                .getDependencyConfigurations("myconf2")))
+        assertEquals([], Arrays.asList(dd.getDependencyConfigurations(["myconf3", "myconf4"])))
+        assertDependencyArtifactIncludeRules(dd, ["myconf1", "myconf2", "myconf3", "myconf4"], [])
+
+        // conf="myconf1->yourconf1myconf2->yourconf1, yourconf2"
+        dd = getDependency(dependencies, "yourmodule5")
+        assertNotNull(dd)
+        assertEquals("yourorg", dd.getDependencyId().getOrganisation())
+        assertEquals("5.1", dd.getDependencyRevisionId().getRevision())
+        assertEquals(["myconf1", "myconf2"] as Set, new HashSet(Arrays.asList(dd.getModuleConfigurations())))
+        assertEquals(["yourconf1"], Arrays.asList(dd.getDependencyConfigurations("myconf1")))
+        assertEquals(["yourconf1", "yourconf2"], Arrays.asList(dd.getDependencyConfigurations("myconf2")))
+        assertEquals([], Arrays.asList(dd.getDependencyConfigurations(["myconf3", "myconf4"])))
+        assertDependencyArtifactIncludeRules(dd, ["myconf1", "myconf2", "myconf3", "myconf4"], [])
+
+        // conf="*->@"
+        dd = getDependency(dependencies, "yourmodule11")
+        assertNotNull(dd)
+        assertEquals("yourorg", dd.getDependencyId().getOrganisation())
+        assertEquals("11.1", dd.getDependencyRevisionId().getRevision())
+        assertEquals(["*"] as Set, new HashSet(Arrays.asList(dd.getModuleConfigurations())))
+        assertEquals(["myconf1"], Arrays.asList(dd.getDependencyConfigurations("myconf1")))
+        assertEquals(["myconf2"], Arrays.asList(dd.getDependencyConfigurations("myconf2")))
+        assertEquals(["myconf3"], Arrays.asList(dd.getDependencyConfigurations("myconf3")))
+        assertEquals(["myconf4"], Arrays.asList(dd.getDependencyConfigurations("myconf4")))
+
+        dd = getDependency(dependencies, "yourmodule6")
+        assertNotNull(dd)
+        assertEquals("yourorg", dd.getDependencyId().getOrganisation())
+        assertEquals("latest.integration", dd.getDependencyRevisionId().getRevision())
+        assertEquals(["myconf1", "myconf2"] as Set, new HashSet(Arrays.asList(dd.getModuleConfigurations())))
+        assertEquals(["yourconf1"], Arrays.asList(dd.getDependencyConfigurations("myconf1")))
+        assertEquals(["yourconf1", "yourconf2"], Arrays.asList(dd.getDependencyConfigurations("myconf2")))
+        assertEquals([], Arrays.asList(dd.getDependencyConfigurations(["myconf3", "myconf4"])))
+        assertDependencyArtifactIncludeRules(dd, ["myconf1", "myconf2", "myconf3", "myconf4"], [])
+
+        dd = getDependency(dependencies, "yourmodule7")
+        assertNotNull(dd)
+        assertEquals("yourorg", dd.getDependencyId().getOrganisation())
+        assertEquals("7.1", dd.getDependencyRevisionId().getRevision())
+        assertEquals(new HashSet(["myconf1", "myconf2"]), new HashSet(
+                Arrays.asList(dd.getModuleConfigurations())))
+        assertEquals(["yourconf1"], Arrays.asList(dd.getDependencyConfigurations("myconf1")))
+        assertEquals(["yourconf1", "yourconf2"], Arrays.asList(dd.getDependencyConfigurations("myconf2")))
+        assertEquals([], Arrays.asList(dd.getDependencyConfigurations(["myconf3", "myconf4"])))
+        assertDependencyArtifactIncludeRules(dd, ["myconf1", "myconf2", "myconf3", "myconf4"], [])
+
+        dd = getDependency(dependencies, "yourmodule8")
+        assertNotNull(dd)
+        assertEquals("yourorg", dd.getDependencyId().getOrganisation())
+        assertEquals("8.1", dd.getDependencyRevisionId().getRevision())
+        assertEquals(["*"] as Set, new HashSet(Arrays.asList(dd.getModuleConfigurations())))
+        assertDependencyArtifacts(dd, ["myconf1"], ["yourartifact8-1", "yourartifact8-2"])
+        assertDependencyArtifacts(dd, ["myconf2"], ["yourartifact8-1", "yourartifact8-2"])
+        assertDependencyArtifacts(dd, ["myconf3"], ["yourartifact8-1", "yourartifact8-2"])
+        assertDependencyArtifacts(dd, ["myconf4"], ["yourartifact8-1", "yourartifact8-2"])
+        dd = getDependency(dependencies, "yourmodule9")
+        assertNotNull(dd)
+        assertEquals("yourorg", dd.getDependencyId().getOrganisation())
+        assertEquals("9.1", dd.getDependencyRevisionId().getRevision())
+        assertEquals(["myconf1", "myconf2", "myconf3"] as Set, new HashSet(Arrays.asList(dd.getModuleConfigurations())))
+        assertDependencyArtifacts(dd, ["myconf1"], ["yourartifact9-1"])
+        assertDependencyArtifacts(dd, ["myconf2"], ["yourartifact9-1", "yourartifact9-2"])
+        assertDependencyArtifacts(dd, ["myconf3"], ["yourartifact9-2"])
+        assertDependencyArtifacts(dd, ["myconf4"], [])
+        assertDependencyArtifactExcludeRules(dd, ["myconf1"], [])
+        assertDependencyArtifactExcludeRules(dd, ["myconf2"], [])
+        assertDependencyArtifactExcludeRules(dd, ["myconf3"], [])
+        assertDependencyArtifactExcludeRules(dd, ["myconf4"], [])
+
+        dd = getDependency(dependencies, "yourmodule10")
+        assertNotNull(dd)
+        assertEquals("yourorg", dd.getDependencyId().getOrganisation())
+        assertEquals("10.1", dd.getDependencyRevisionId().getRevision())
+        assertEquals(new HashSet(["*"]), new HashSet(Arrays.asList(dd.getModuleConfigurations())))
+        assertDependencyArtifactIncludeRules(dd, ["myconf1"], ["your.*", PatternMatcher.ANY_EXPRESSION])
+        assertDependencyArtifactIncludeRules(dd, ["myconf2"], ["your.*", PatternMatcher.ANY_EXPRESSION])
+        assertDependencyArtifactIncludeRules(dd, ["myconf3"], ["your.*", PatternMatcher.ANY_EXPRESSION])
+        assertDependencyArtifactIncludeRules(dd, ["myconf4"], ["your.*", PatternMatcher.ANY_EXPRESSION])
+        assertDependencyArtifactExcludeRules(dd, ["myconf1"], ["toexclude"])
+        assertDependencyArtifactExcludeRules(dd, ["myconf2"], ["toexclude"])
+        assertDependencyArtifactExcludeRules(dd, ["myconf3"], ["toexclude"])
+        assertDependencyArtifactExcludeRules(dd, ["myconf4"], ["toexclude"])
+        true
+    }
+
+    void assertDependencyArtifactExcludeRules(DependencyDescriptor dd, List<String> confs,
+                                              List<String> artifactsNames) {
+        ExcludeRule[] rules = dd.getExcludeRules(confs.toArray(new String[confs.size()]))
+        assertNotNull(rules)
+        assertEquals(artifactsNames.size(), rules.length)
+        for (String artifactName : artifactsNames) {
+            boolean found = false
+            for (int j = 0; j < rules.length; j++) {
+                assertNotNull(rules[j])
+                if (rules[j].getId().getName().equals(artifactName)) {
+                    found = true
+                    break
+                }
+            }
+            assertTrue("dependency exclude not found: " + artifactName, found)
+        }
+    }
+
+    protected void assertDependencyArtifactIncludeRules(DependencyDescriptor dd, List<String> confs,
+                                                        List<String> artifactsNames) {
+        IncludeRule[] dads = dd.getIncludeRules(confs.toArray(new String[confs.size()]))
+        assertNotNull(dads)
+        assertEquals(artifactsNames.size(), dads.length)
+        for (String artifactName : artifactsNames) {
+            boolean found = false
+            for (int j = 0; j < dads.length; j++) {
+                assertNotNull(dads[j])
+                if (dads[j].getId().getName().equals(artifactName)) {
+                    found = true
+                    break
+                }
+            }
+            assertTrue("dependency include not found: " + artifactName, found)
+        }
+    }
+
+    protected void assertArtifacts(Artifact[] artifacts, String[] artifactsNames) {
+        assertNotNull(artifacts)
+        assertEquals(artifactsNames.length, artifacts.length)
+        for (int i = 0; i < artifactsNames.length; i++) {
+            boolean found = false
+            for (int j = 0; j < artifacts.length; j++) {
+                assertNotNull(artifacts[j])
+                if (artifacts[j].getName().equals(artifactsNames[i])) {
+                    found = true
+                    break
+                }
+            }
+            assertTrue("artifact not found: " + artifactsNames[i], found)
+        }
+    }
+
+    protected void assertConf(ModuleDescriptor md, String name, String desc, Configuration.Visibility visibility,
+                              String[] exts) {
+        Configuration conf = md.getConfiguration(name)
+        assertNotNull("configuration not found: " + name, conf)
+        assertEquals(name, conf.getName())
+        assertEquals(desc, conf.getDescription())
+        assertEquals(visibility, conf.getVisibility())
+        assertEquals(Arrays.asList(exts), Arrays.asList(conf.getExtends()))
+    }
+
+    protected DependencyDescriptor getDependency(DependencyDescriptor[] dependencies, String name) {
+        for (int i = 0; i < dependencies.length; i++) {
+            assertNotNull(dependencies[i])
+            assertNotNull(dependencies[i].getDependencyId())
+            if (name.equals(dependencies[i].getDependencyId().getName())) {
+                return dependencies[i]
+            }
+        }
+        return null
+    }
+
+    protected void assertDependencyArtifacts(DependencyDescriptor dd, List<String> confs,
+                                             List<String> artifactsNames) {
+        DependencyArtifactDescriptor[] dads = dd.getDependencyArtifacts(confs.toArray(new String[confs.size()]));
+        assertNotNull(dads);
+        assertEquals(artifactsNames.size(), dads.length);
+        for (String artifactName : artifactsNames) {
+            boolean found = false;
+            for (int j = 0; j < dads.length; j++) {
+                assertNotNull(dads[j]);
+                if (dads[j].getName().equals(artifactName)) {
+                    found = true;
+                    break;
+                }
+            }
+            assertTrue("dependency artifact not found: " + artifactName, found);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorStoreTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorStoreTest.groovy
index 2d4d058..b5f32cb 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorStoreTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorStoreTest.groovy
@@ -18,18 +18,19 @@ package org.gradle.api.internal.artifacts.ivyservice.modulecache
 
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor
 import org.apache.ivy.core.module.id.ModuleRevisionId
-import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser
+import org.gradle.api.artifacts.ModuleVersionIdentifier
 import org.gradle.api.internal.artifacts.ivyservice.IvyModuleDescriptorWriter
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.IvyXmlModuleDescriptorParser
 import org.gradle.api.internal.filestore.FileStoreEntry
 import org.gradle.api.internal.filestore.PathKeyFileStore
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 class ModuleDescriptorStoreTest extends Specification {
 
-    @Rule TemporaryFolder temporaryFolder
+    @Rule TestNameTestDirectoryProvider temporaryFolder
     ModuleDescriptorStore store
     PathKeyFileStore pathKeyFileStore = Mock()
     ModuleRevisionId moduleRevisionId = Mock()
@@ -37,38 +38,42 @@ class ModuleDescriptorStoreTest extends Specification {
     FileStoreEntry fileStoreEntry = Mock()
     ModuleDescriptor moduleDescriptor = Mock()
     IvyModuleDescriptorWriter ivyModuleDescriptorWriter = Mock()
-    XmlModuleDescriptorParser xmlModuleDescriptorParser = Mock()
+    IvyXmlModuleDescriptorParser ivyXmlModuleDescriptorParser = Mock()
+    ModuleVersionIdentifier moduleVersionIdentifier = Mock()
 
     def setup() {
-        store = new ModuleDescriptorStore(pathKeyFileStore, ivyModuleDescriptorWriter, xmlModuleDescriptorParser);
+        store = new ModuleDescriptorStore(pathKeyFileStore, ivyModuleDescriptorWriter, ivyXmlModuleDescriptorParser);
         _ * repository.getId() >> "repositoryId"
-        _ * moduleRevisionId.getOrganisation() >> "org.test"
-        _ * moduleRevisionId.getName() >> "testArtifact"
-        _ * moduleRevisionId.getRevision() >> "1.0"
+        _ * moduleVersionIdentifier.group >> "org.test"
+        _ * moduleVersionIdentifier.name >> "testArtifact"
+        _ * moduleVersionIdentifier.version >> "1.0"
         _ * moduleDescriptor.getModuleRevisionId() >> moduleRevisionId
     }
 
     def "getModuleDescriptorFile returns null for not cached descriptors"() {
         when:
-        pathKeyFileStore.get("module-metadata/org.test/testArtifact/1.0/repositoryId.ivy.xml") >> null
+        pathKeyFileStore.get("module-metadata/org.test/testArtifact/1.0/repositoryId/ivy.xml") >> null
         then:
-        null == store.getModuleDescriptor(repository, moduleRevisionId)
+        null == store.getModuleDescriptor(repository, moduleVersionIdentifier)
     }
 
     def "getModuleDescriptorFile uses PathKeyFileStore to get file"() {
         when:
-        store.getModuleDescriptor(repository, moduleRevisionId);
+        store.getModuleDescriptor(repository, moduleVersionIdentifier);
         then:
-        1 * pathKeyFileStore.get("module-metadata/org.test/testArtifact/1.0/repositoryId.ivy.xml") >> null
+        1 * pathKeyFileStore.get("module-metadata/org.test/testArtifact/1.0/repositoryId/ivy.xml") >> null
     }
 
     def "putModuleDescriptor uses PathKeyFileStore to write file"() {
         setup:
+        _ * moduleRevisionId.organisation >> "org.test"
+        _ * moduleRevisionId.name >> "testArtifact"
+        _ * moduleRevisionId.revision >> "1.0"
         File descriptorFile = temporaryFolder.createFile("fileStoreEntry")
         when:
         store.putModuleDescriptor(repository, moduleDescriptor);
         then:
-        1 * pathKeyFileStore.add("module-metadata/org.test/testArtifact/1.0/repositoryId.ivy.xml", _) >> {path, action ->
+        1 * pathKeyFileStore.add("module-metadata/org.test/testArtifact/1.0/repositoryId/ivy.xml", _) >> { path, action ->
             action.execute(descriptorFile); fileStoreEntry
         };
         1 * ivyModuleDescriptorWriter.write(moduleDescriptor, descriptorFile)
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolverTest.groovy
index 9dc6f03..6a90639 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolverTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolverTest.groovy
@@ -18,6 +18,7 @@ package org.gradle.api.internal.artifacts.ivyservice.projectmodule
 import org.apache.ivy.core.module.descriptor.DependencyDescriptor
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor
 import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.gradle.api.artifacts.ModuleVersionIdentifier
 import org.gradle.api.internal.artifacts.ivyservice.BuildableModuleVersionResolveResult
 import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver
 import spock.lang.Specification
@@ -28,8 +29,13 @@ class ProjectDependencyResolverTest extends Specification {
     final ModuleRevisionId moduleRevisionId = Mock()
     final DependencyToModuleResolver target = Mock()
     final ProjectDependencyResolver resolver = new ProjectDependencyResolver(registry, target)
-    
+
     def "resolves project dependency"() {
+        setup:
+        1 * moduleRevisionId.organisation >> "group"
+        1 * moduleRevisionId.name >> "project"
+        1 * moduleRevisionId.revision >> "1.0"
+
         ModuleDescriptor moduleDescriptor = Mock()
         BuildableModuleVersionResolveResult result = Mock()
 
@@ -39,7 +45,12 @@ class ProjectDependencyResolverTest extends Specification {
         then:
         1 * registry.findProject(dependencyDescriptor) >> moduleDescriptor
         _ * moduleDescriptor.moduleRevisionId >> moduleRevisionId
-        1 * result.resolved(moduleRevisionId, moduleDescriptor, _)
+        1 * result.resolved(_, moduleDescriptor, _) >> { args ->
+            ModuleVersionIdentifier moduleVersionIdentifier = args[0]
+            moduleVersionIdentifier.group == "group"
+            moduleVersionIdentifier.name == "project"
+            moduleVersionIdentifier.version == "1.0"
+        }
         0 * result._
     }
 
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicySpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicySpec.groovy
new file mode 100644
index 0000000..10664b7
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultCachePolicySpec.groovy
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy;
+
+
+import org.gradle.api.Action
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.artifacts.ResolvedModuleVersion
+import org.gradle.api.artifacts.cache.ArtifactResolutionControl
+import org.gradle.api.artifacts.cache.DependencyResolutionControl
+import org.gradle.api.artifacts.cache.ModuleResolutionControl
+import org.gradle.api.internal.artifacts.DefaultArtifactIdentifier
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
+import spock.lang.Specification
+
+import java.util.concurrent.TimeUnit
+
+import static org.gradle.util.Assertions.assertThat
+
+public class DefaultCachePolicySpec extends Specification {
+    private static final int SECOND = 1000;
+    private static final int MINUTE = SECOND * 60;
+    private static final int HOUR = MINUTE * 60;
+    private static final int DAY = HOUR * 24;
+    private static final int WEEK = DAY * 7;
+    private static final int FOREVER = Integer.MAX_VALUE
+
+    DefaultCachePolicy cachePolicy = new DefaultCachePolicy()
+
+    def "will cache default"() {
+        expect:
+        hasDynamicVersionTimeout(DAY)
+        hasChangingModuleTimeout(DAY)
+        hasModuleTimeout(FOREVER)
+        hasMissingArtifactTimeout(DAY)
+        hasMissingModuleTimeout(DAY)
+    }
+
+    def "uses changing module timeout for changing modules"() {
+        when:
+        cachePolicy.cacheChangingModulesFor(10, TimeUnit.SECONDS);
+
+        then:
+        hasDynamicVersionTimeout(DAY);
+        hasChangingModuleTimeout(10 * SECOND)
+        hasModuleTimeout(FOREVER)
+        hasMissingModuleTimeout(DAY)
+        hasChangingArtifactTimeout(DAY)
+    }
+
+    def "uses dynamic version timeout for dynamic versions"() {
+        when:
+        cachePolicy.cacheDynamicVersionsFor(10, TimeUnit.SECONDS)
+
+        then:
+        hasDynamicVersionTimeout(10 * SECOND)
+        hasChangingModuleTimeout(DAY)
+        hasMissingModuleTimeout(DAY)
+        hasMissingArtifactTimeout(DAY)
+        hasModuleTimeout(FOREVER)
+    }
+
+    def "applies invalidate rule for dynamic versions"() {
+        when:
+        cachePolicy.eachDependency(new Action<DependencyResolutionControl>() {
+            void execute(DependencyResolutionControl t) {
+                t.refresh()
+            }
+        })
+
+        then:
+        cachePolicy.mustRefreshDynamicVersion(null, null, 2 * SECOND)
+    }
+
+    def "applies useCachedResult for dynamic versions"() {
+        when:
+        cachePolicy.eachDependency(new Action<DependencyResolutionControl>() {
+            void execute(DependencyResolutionControl t) {
+                t.useCachedResult()
+            }
+        })
+
+        then:
+        !cachePolicy.mustRefreshDynamicVersion(null, null, 2 * SECOND)
+    }
+
+    def "applies cacheFor rules for dynamic versions"() {
+        when:
+        cachePolicy.eachDependency(new Action<DependencyResolutionControl>() {
+            void execute(DependencyResolutionControl t) {
+                t.cacheFor(100, TimeUnit.SECONDS)
+            }
+        })
+
+        then:
+        hasDynamicVersionTimeout(100 * SECOND)
+    }
+
+    def "provides details of cached dynamic version"() {
+        expect:
+        cachePolicy.eachDependency(new Action<DependencyResolutionControl>() {
+            void execute(DependencyResolutionControl t) {
+                assertId(t.request, 'g', 'n', 'v')
+                assertId(t.cachedResult, 'group', 'name', 'version')
+                t.refresh()
+            }
+        })
+        cachePolicy.mustRefreshDynamicVersion(moduleSelector('g', 'n', 'v'), moduleIdentifier('group', 'name', 'version'), 0)
+    }
+
+    def "provides details of cached module"() {
+        expect:
+        cachePolicy.eachModule(new Action<ModuleResolutionControl>() {
+            void execute(ModuleResolutionControl t) {
+                assertId(t.request, 'g', 'n', 'v')
+                assertId(t.cachedResult.id, 'group', 'name', 'version')
+                assert !t.changing
+                t.refresh()
+            }
+        })
+        cachePolicy.mustRefreshModule(moduleIdentifier('g', 'n', 'v'), moduleVersion('group', 'name', 'version'), null, 0)
+    }
+
+    def "provides details of cached changing module"() {
+        expect:
+        cachePolicy.eachModule(new Action<ModuleResolutionControl>() {
+            void execute(ModuleResolutionControl t) {
+                assertId(t.request, 'g', 'n', 'v')
+                assertId(t.cachedResult.id, 'group', 'name', 'version')
+                assert t.changing
+                t.refresh()
+            }
+        })
+        cachePolicy.mustRefreshChangingModule(moduleIdentifier('g', 'n', 'v'), moduleVersion('group', 'name', 'version'), 0)
+    }
+
+    def "provides details of cached artifact"() {
+        expect:
+        cachePolicy.eachArtifact(new Action<ArtifactResolutionControl>() {
+            void execute(ArtifactResolutionControl t) {
+                assertId(t.request.moduleVersionIdentifier, 'group', 'name', 'version')
+                assert t.request.name == 'artifact'
+                assert t.request.type == 'type'
+                assert t.request.extension == 'ext'
+                assert t.request.classifier == 'classifier'
+                assert t.cachedResult == null
+                t.refresh()
+            }
+        })
+        def artifactIdentifier = new DefaultArtifactIdentifier(moduleIdentifier('group', 'name', 'version'), 'artifact', 'type', 'ext', 'classifier')
+        cachePolicy.mustRefreshArtifact(artifactIdentifier, null, 0, true, true)
+    }
+
+    def "can use cacheFor to control missing module and artifact timeout"() {
+        when:
+        cachePolicy.eachModule(new Action<ModuleResolutionControl>() {
+            void execute(ModuleResolutionControl t) {
+                if (t.cachedResult == null) {
+                    t.cacheFor(10, TimeUnit.SECONDS)
+                }
+            }
+        });
+        cachePolicy.eachArtifact(new Action<ArtifactResolutionControl>() {
+            void execute(ArtifactResolutionControl t) {
+                if (t.cachedResult == null) {
+                    t.cacheFor(20, TimeUnit.SECONDS)
+                }
+            }
+        });
+
+        then:
+        hasDynamicVersionTimeout(DAY)
+        hasChangingModuleTimeout(DAY)
+        hasModuleTimeout(FOREVER)
+        hasMissingModuleTimeout(10 * SECOND)
+        hasMissingArtifactTimeout(20 * SECOND)
+    }
+
+    def "must refresh artifact for changing modules when moduledescriptorhash not in sync"() {
+        expect:
+        !cachePolicy.mustRefreshArtifact(null, null, 1000, false, true)
+        !cachePolicy.mustRefreshArtifact(null, null, 1000, false, false)
+        cachePolicy.mustRefreshArtifact(null, null, 1000, true, false)
+    }
+
+    def "provides a copy"() {
+        expect:
+        def copy = cachePolicy.copy()
+
+        !copy.is(cachePolicy)
+        assertThat(copy).doesNotShareStateWith(cachePolicy)
+
+        copy.dependencyCacheRules == cachePolicy.dependencyCacheRules
+        copy.moduleCacheRules == cachePolicy.moduleCacheRules
+        copy.artifactCacheRules == cachePolicy.artifactCacheRules
+    }
+
+    private def hasDynamicVersionTimeout(int timeout) {
+        def moduleId = moduleIdentifier('group', 'name', 'version')
+        assert !cachePolicy.mustRefreshDynamicVersion(null, moduleId, 100)
+        assert !cachePolicy.mustRefreshDynamicVersion(null, moduleId, timeout);
+        assert !cachePolicy.mustRefreshDynamicVersion(null, moduleId, timeout - 1)
+        cachePolicy.mustRefreshDynamicVersion(null, moduleId, timeout + 1)
+    }
+
+    private def hasChangingModuleTimeout(int timeout) {
+        def module = moduleVersion('group', 'name', 'version')
+        assert !cachePolicy.mustRefreshChangingModule(null, module, timeout - 1)
+        assert !cachePolicy.mustRefreshChangingModule(null, module, timeout);
+        cachePolicy.mustRefreshChangingModule(null, module, timeout + 1)
+    }
+
+    private def hasModuleTimeout(int timeout) {
+        def module = moduleVersion('group', 'name', 'version')
+        assert !cachePolicy.mustRefreshModule(null, module, null, timeout);
+        assert !cachePolicy.mustRefreshModule(null, module, null, timeout - 1)
+        if (timeout == FOREVER) {
+            return true
+        }
+        cachePolicy.mustRefreshModule(null, module, timeout + 1)
+    }
+
+    private def hasMissingModuleTimeout(int timeout) {
+        assert !cachePolicy.mustRefreshModule(null, null, null, timeout);
+        assert !cachePolicy.mustRefreshModule(null, null, null, timeout - 1)
+        cachePolicy.mustRefreshModule(null, null, null, timeout + 1)
+    }
+
+    private def hasChangingArtifactTimeout(int timeout){
+        cachePolicy.mustRefreshArtifact(null, null, timeout, true, true)
+    }
+
+    private def hasMissingArtifactTimeout(int timeout) {
+        assert !cachePolicy.mustRefreshArtifact(null, null, timeout, true, true);
+        assert !cachePolicy.mustRefreshArtifact(null, null, timeout - 1, true, true)
+        cachePolicy.mustRefreshArtifact(null, null, timeout + 1, true, true)
+    }
+
+    private def assertId(def moduleId, String group, String name, String version) {
+        assert moduleId.group == group
+        assert moduleId.name == name
+        assert moduleId.version == version
+    }
+
+    private def moduleSelector(String group, String name, String version) {
+        new DefaultModuleVersionSelector(group, name, version)
+    }
+
+    private def moduleIdentifier(String group, String name, String version) {
+        new DefaultModuleVersionIdentifier(group, name, version)
+    }
+
+    private def moduleVersion(String group, String name, String version) {
+        return new ResolvedModuleVersion() {
+            ModuleVersionIdentifier getId() {
+                return new DefaultModuleVersionIdentifier(group, name, version);
+            }
+        }
+    }
+
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategySpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategySpec.groovy
new file mode 100644
index 0000000..d5df830
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategySpec.groovy
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy;
+
+
+import org.gradle.api.Action
+import org.gradle.api.internal.artifacts.DependencyResolveDetailsInternal
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+import static org.gradle.util.Assertions.assertThat
+
+/**
+ * by Szczepan Faber, created at: 11/2/11
+ */
+public class DefaultResolutionStrategySpec extends Specification {
+
+    def strategy = new DefaultResolutionStrategy()
+
+    def "allows setting forced modules"() {
+        expect:
+        strategy.forcedModules.empty
+
+        when:
+        strategy.force 'org.foo:bar:1.0', 'org.foo:baz:2.0'
+
+        then:
+        def versions = strategy.forcedModules as List
+        versions.size() == 2
+
+        versions[0].group == 'org.foo'
+        versions[0].name == 'bar'
+        versions[0].version == '1.0'
+
+        versions[1].group == 'org.foo'
+        versions[1].name == 'baz'
+        versions[1].version == '2.0'
+    }
+
+    def "allows replacing forced modules"() {
+        given:
+        strategy.force 'org.foo:bar:1.0'
+
+        when:
+        strategy.forcedModules = ['hello:world:1.0', [group:'g', name:'n', version:'1']]
+
+        then:
+        def versions = strategy.forcedModules as List
+        versions.size() == 2
+        versions[0].group == 'hello'
+        versions[1].group == 'g'
+    }
+
+    def "provides no op resolve rule when no rules or forced modules configured"() {
+        given:
+        def details = Mock(DependencyResolveDetailsInternal)
+
+        when:
+        strategy.dependencyResolveRule.execute(details)
+
+        then:
+        0 * details._
+    }
+
+    def "provides dependency resolve rule that forces modules"() {
+        given:
+        strategy.force 'org:bar:1.0', 'org:foo:2.0'
+        def details = Mock(DependencyResolveDetailsInternal)
+
+        when:
+        strategy.dependencyResolveRule.execute(details)
+
+        then:
+        _ * details.getRequested() >> newSelector("org", "foo", "1.0")
+        1 * details.useVersion("2.0", VersionSelectionReasons.FORCED)
+        0 * details._
+    }
+
+    def "provides dependency resolve rule that orderly aggregates user specified rules"() {
+        given:
+        strategy.eachDependency({ it.useVersion("1.0") } as Action)
+        strategy.eachDependency({ it.useVersion("2.0") } as Action)
+        def details = Mock(DependencyResolveDetailsInternal)
+
+        when:
+        strategy.dependencyResolveRule.execute(details)
+
+        then:
+        1 * details.useVersion("1.0")
+        then:
+        1 * details.useVersion("2.0")
+        0 * details._
+    }
+
+    def "provides dependency resolve rule with forced modules first and then user specified rules"() {
+        given:
+        strategy.force 'org:bar:1.0', 'org:foo:2.0'
+        strategy.eachDependency({ it.useVersion("5.0") } as Action)
+        strategy.eachDependency({ it.useVersion("6.0") } as Action)
+
+        def details = Mock(DependencyResolveDetailsInternal)
+
+        when:
+        strategy.dependencyResolveRule.execute(details)
+
+        then: //forced modules:
+        _ * details.requested >> newSelector("org", "foo", "1.0")
+        1 * details.useVersion("2.0", VersionSelectionReasons.FORCED)
+
+        then: //user rules, in order:
+        1 * details.useVersion("5.0")
+        then:
+        1 * details.useVersion("6.0")
+        0 * details._
+    }
+
+    def "copied instance does not share state"() {
+        when:
+        def copy = strategy.copy()
+
+        then:
+        !copy.is(strategy)
+        assertThat(copy).doesNotShareStateWith(strategy)
+    }
+
+    def "provides a copy"() {
+        given:
+        def newCachePolicy = Mock(DefaultCachePolicy)
+        def cachePolicy = Mock(DefaultCachePolicy) {
+            copy() >> newCachePolicy
+        }
+
+        strategy = new DefaultResolutionStrategy(cachePolicy, [] as Set)
+        strategy.failOnVersionConflict()
+        strategy.force("org:foo:1.0")
+        strategy.eachDependency(Mock(Action))
+
+        when:
+        def copy = strategy.copy()
+
+        then:
+        copy.forcedModules == strategy.forcedModules
+        copy.dependencyResolveRules == strategy.dependencyResolveRules
+        copy.conflictResolution instanceof StrictConflictResolution
+
+        strategy.cachePolicy == cachePolicy
+        copy.cachePolicy == newCachePolicy
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRuleSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRuleSpec.groovy
new file mode 100644
index 0000000..29b75eb
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/ModuleForcingResolveRuleSpec.groovy
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy
+
+import org.gradle.api.internal.artifacts.DependencyResolveDetailsInternal
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+
+/**
+ * by Szczepan Faber, created at: 11/29/12
+ */
+class ModuleForcingResolveRuleSpec extends Specification {
+
+    def "forces modules"() {
+        given:
+        def details = Mock(DependencyResolveDetailsInternal)
+
+        when:
+        new ModuleForcingResolveRule([
+            newSelector("org",  "module1", "1.0"),
+            newSelector("org",  "module2", "2.0"),
+            //exotic module with colon in the name
+            newSelector("org",  "module:with:colon", "3.0"),
+            newSelector("org:with:colon",  "module2", "4.0")
+        ]).execute(details)
+
+        then:
+        _ * details.getRequested() >> requested
+        1 * details.useVersion(forcedVersion, VersionSelectionReasons.FORCED)
+        0 * details._
+
+        where:
+        requested                                        | forcedVersion
+        newSelector("org",  "module2", "0.9")            | "2.0"
+        newSelector("org",  "module2", "2.1")            | "2.0"
+        newSelector("org",  "module:with:colon", "2.0")  | "3.0"
+        newSelector("org:with:colon",  "module2", "5.0") | "4.0"
+    }
+
+    def "does not force modules if they dont match"() {
+        given:
+        def details = Mock(DependencyResolveDetailsInternal)
+
+        when:
+        new ModuleForcingResolveRule([
+            newSelector("org",  "module1", "1.0"),
+            newSelector("org",  "module2", "2.0"),
+            newSelector("org",  "module:with:colon", "3.0"),
+            newSelector("org:with:colon",  "module2", "4.0")
+        ]).execute(details)
+
+        then:
+        _ * details.getRequested() >> requested
+        0 * details._
+
+        where:
+        requested << [
+            newSelector("orgX", "module2", "0.9"),
+            newSelector("org",  "moduleX", "2.9"),
+            newSelector("orgX",  "module:with:colon", "2.9"),
+            newSelector("org:with:colon",  "moduleX", "2.9"),
+            newSelector("org:with",  "colon:module2", "2.9"),
+            newSelector("org",  "with:colon:module2", "2.9"),
+        ]
+    }
+
+    def "does not force anything when input empty"() {
+        def details = Mock(DependencyResolveDetailsInternal)
+
+        when:
+        new ModuleForcingResolveRule([]).execute(details)
+
+        then:
+        0 * details._
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy
index a553d0d..7d428b5 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy
@@ -15,6 +15,7 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine
 
+import org.apache.ivy.core.module.descriptor.*
 import org.apache.ivy.core.module.id.ArtifactId
 import org.apache.ivy.core.module.id.ModuleId
 import org.apache.ivy.core.module.id.ModuleRevisionId
@@ -23,19 +24,20 @@ import org.apache.ivy.core.resolve.ResolveEngine
 import org.apache.ivy.core.resolve.ResolveOptions
 import org.apache.ivy.plugins.matcher.ExactPatternMatcher
 import org.apache.ivy.plugins.matcher.PatternMatcher
+import org.gradle.api.artifacts.*
 import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
 import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
 import org.gradle.api.internal.artifacts.DefaultResolvedArtifact
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
+import org.gradle.api.internal.artifacts.ivyservice.*
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.EnhancedDependencyDescriptor
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ResolvedConfigurationListener
 import org.gradle.api.specs.Spec
 import spock.lang.Specification
-import org.apache.ivy.core.module.descriptor.*
-import org.gradle.api.artifacts.*
-import org.gradle.api.internal.artifacts.ivyservice.*
 
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
 
 class DependencyGraphBuilderTest extends Specification {
     final ModuleDescriptorConverter moduleDescriptorConverter = Mock()
@@ -93,9 +95,9 @@ class DependencyGraphBuilderTest extends Specification {
         then:
         1 * listener.start(newId("group", "root", "1.0"))
         then:
-        1 * listener.resolvedConfiguration({ it.name == 'root' }, { it*.requested.name == ['a', 'b'] } )
+        1 * listener.resolvedConfiguration({ it.name == 'root' }, { it*.requested.name == ['a', 'b'] })
         then:
-        1 * listener.resolvedConfiguration({ it.name == 'a' },    { it*.requested.name == ['c', 'd'] && it*.failure.count { it != null } == 1 } )
+        1 * listener.resolvedConfiguration({ it.name == 'a' }, { it*.requested.name == ['c', 'd'] && it*.failure.count { it != null } == 1 })
     }
 
     def "does not resolve a given dynamic module selector more than once"() {
@@ -284,13 +286,13 @@ class DependencyGraphBuilderTest extends Specification {
         result.rethrowFailure()
 
         then:
-        1 * conflictResolver.select({it*.revision == ['1.1', '1.2']}, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+        1 * conflictResolver.select({ it*.revision == ['1.1', '1.2'] }, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
             return candidates.find { it.revision == '1.2' }
         }
-        1 * conflictResolver.select({it*.revision == ['2.1', '2.2']}, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+        1 * conflictResolver.select({ it*.revision == ['2.1', '2.2'] }, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
             return candidates.find { it.revision == '2.2' }
         }
-        1 * conflictResolver.select({it*.revision == ['1.1', '1.2', '1.0']}, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+        1 * conflictResolver.select({ it*.revision == ['1.1', '1.2', '1.0'] }, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
             return candidates.find { it.revision == '1.2' }
         }
         0 * conflictResolver._
@@ -320,7 +322,7 @@ class DependencyGraphBuilderTest extends Specification {
         result.rethrowFailure()
 
         then:
-        1 * conflictResolver.select({it*.revision == ['1', '2']}, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+        1 * conflictResolver.select({ it*.revision == ['1', '2'] }, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
             return candidates.find { it.revision == '2' }
         }
         0 * conflictResolver._
@@ -350,7 +352,7 @@ class DependencyGraphBuilderTest extends Specification {
         result.rethrowFailure()
 
         then:
-        1 * conflictResolver.select({it*.revision == ['1', '2']}, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+        1 * conflictResolver.select({ it*.revision == ['1', '2'] }, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
             return candidates.find { it.revision == '2' }
         }
         0 * conflictResolver._
@@ -665,7 +667,7 @@ class DependencyGraphBuilderTest extends Specification {
         e.cause.message.contains "group:root:1.0 > group:a:1.0"
         e.cause.message.contains "group:root:1.0 > group:b:1.0"
     }
-    
+
     def "can handle a cycle in the incoming paths of a broken module"() {
         given:
         def a = revision('a')
@@ -833,10 +835,11 @@ class DependencyGraphBuilderTest extends Specification {
 
     def traverses(Map<String, ?> args = [:], DefaultModuleDescriptor from, DefaultModuleDescriptor to) {
         def descriptor = dependsOn(args, from, to.moduleRevisionId)
-        def idResolveResult = selectorResolvesTo(descriptor, to.moduleRevisionId)
+        def moduleVersionIdentifier = toModuleVersionIdentifier(to.moduleRevisionId)
+        def idResolveResult = selectorResolvesTo(descriptor, moduleVersionIdentifier);
         ModuleVersionResolveResult resolveResult = Mock()
         1 * idResolveResult.resolve() >> resolveResult
-        1 * resolveResult.id >> to.moduleRevisionId
+        1 * resolveResult.id >> moduleVersionIdentifier
         1 * resolveResult.descriptor >> { println "RESOLVE $from.moduleRevisionId -> $to.moduleRevisionId"; return to }
     }
 
@@ -844,30 +847,41 @@ class DependencyGraphBuilderTest extends Specification {
         def descriptor = dependsOn(args, from, to.moduleRevisionId)
         ModuleVersionIdResolveResult result = Mock()
         (0..1) * dependencyResolver.resolve(descriptor) >> result
-        (0..1) * result.id >> to.moduleRevisionId
+        (0..1) * result.id >> toModuleVersionIdentifier(to.moduleRevisionId);
     }
 
     def traversesMissing(Map<String, ?> args = [:], DefaultModuleDescriptor from, DefaultModuleDescriptor to) {
         def descriptor = dependsOn(args, from, to.moduleRevisionId)
-        def idResolveResult = selectorResolvesTo(descriptor, to.moduleRevisionId)
+        def moduleVersionIdentifier = toModuleVersionIdentifier(to.moduleRevisionId)
+        def idResolveResult = selectorResolvesTo(descriptor, moduleVersionIdentifier)
         ModuleVersionResolveResult resolveResult = Mock()
         1 * idResolveResult.resolve() >> resolveResult
-        1 * resolveResult.id >> { throw new ModuleVersionNotFoundException("missing") }
+        1 * resolveResult.id >> { throw new ModuleVersionNotFoundException(newId("org", "a", "1.2")) }
     }
 
     def traversesBroken(Map<String, ?> args = [:], DefaultModuleDescriptor from, DefaultModuleDescriptor to) {
         def descriptor = dependsOn(args, from, to.moduleRevisionId)
-        def idResolveResult = selectorResolvesTo(descriptor, to.moduleRevisionId)
+        def moduleVersionIdentifier = toModuleVersionIdentifier(to.moduleRevisionId)
+        def idResolveResult = selectorResolvesTo(descriptor, moduleVersionIdentifier)
         ModuleVersionResolveResult resolveResult = Mock()
         1 * idResolveResult.resolve() >> resolveResult
-        1 * resolveResult.id >> { throw new ModuleVersionResolveException("broken") }
+        1 * resolveResult.id >> { throw new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken") }
+    }
+
+    ModuleVersionIdentifier toModuleVersionIdentifier(ModuleRevisionId moduleRevisionId) {
+        ModuleVersionIdentifier moduleVersionIdentifier = Mock();
+        (0..2) * moduleVersionIdentifier.group >> moduleRevisionId.organisation;
+        (0..2) * moduleVersionIdentifier.name >> moduleRevisionId.name;
+        (0..2) * moduleVersionIdentifier.version >> moduleRevisionId.revision;
+        moduleVersionIdentifier
     }
 
     def brokenSelector(Map<String, ?> args = [:], DefaultModuleDescriptor from, String to) {
         def descriptor = dependsOn(args, from, ModuleRevisionId.newInstance("group", to, "1.0"))
         ModuleVersionIdResolveResult result = Mock()
         (0..1) * dependencyResolver.resolve(descriptor) >> result
-        _ * result.failure >> new ModuleVersionResolveException("broken")
+        _ * result.failure >> new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
+        _ * result.selectionReason >> VersionSelectionReasons.REQUESTED
         0 * result._
     }
 
@@ -889,7 +903,7 @@ class DependencyGraphBuilderTest extends Specification {
         return descriptor
     }
 
-    def selectorResolvesTo(DependencyDescriptor descriptor, ModuleRevisionId to) {
+    def selectorResolvesTo(DependencyDescriptor descriptor, ModuleVersionIdentifier to) {
         ModuleVersionIdResolveResult result = Mock()
         1 * dependencyResolver.resolve(descriptor) >> result
         1 * result.id >> to
@@ -897,13 +911,13 @@ class DependencyGraphBuilderTest extends Specification {
     }
 
     def ids(ModuleDescriptor... descriptors) {
-        return descriptors.collect { new DefaultModuleVersionIdentifier(it.moduleRevisionId.organisation, it.moduleRevisionId.name, it.moduleRevisionId.revision)} as Set
+        return descriptors.collect { new DefaultModuleVersionIdentifier(it.moduleRevisionId.organisation, it.moduleRevisionId.name, it.moduleRevisionId.revision) } as Set
     }
 
     def modules(LenientConfiguration config) {
         Set<ModuleVersionIdentifier> result = new LinkedHashSet<ModuleVersionIdentifier>()
         List<ResolvedDependency> queue = []
-        queue.addAll(config.getFirstLevelModuleDependencies({true} as Spec))
+        queue.addAll(config.getFirstLevelModuleDependencies({ true } as Spec))
         while (!queue.empty) {
             def node = queue.remove(0)
             result.add(node.module.id)
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/CachingDependencyResultFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/CachingDependencyResultFactoryTest.groovy
index 7a5dbd5..1c1521c 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/CachingDependencyResultFactoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/CachingDependencyResultFactoryTest.groovy
@@ -20,6 +20,8 @@ import spock.lang.Specification
 
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
 import static org.gradle.api.internal.artifacts.result.ResolutionResultDataBuilder.newModule
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason
 
 /**
  * by Szczepan Faber, created at: 10/1/12
@@ -49,14 +51,15 @@ class CachingDependencyResultFactoryTest extends Specification {
 
     def "creates and caches unresolved dependencies"() {
         def fromModule = newModule('from')
+        def selectedModule = Mock(ModuleVersionSelectionReason)
 
         when:
-        def dep = factory.createUnresolvedDependency(selector('requested'), fromModule, new RuntimeException("foo"))
-        def same = factory.createUnresolvedDependency(selector('requested'), fromModule, new RuntimeException("foo"))
+        def dep = factory.createUnresolvedDependency(selector('requested'), fromModule, selectedModule, new ModuleVersionResolveException(selector('requested'), "foo"))
+        def same = factory.createUnresolvedDependency(selector('requested'), fromModule, selectedModule, new ModuleVersionResolveException(selector('requested'), "foo"))
 
-        def differentRequested = factory.createUnresolvedDependency(selector('xxx'), fromModule, new RuntimeException("foo"))
-        def differentFrom = factory.createUnresolvedDependency(selector('requested'), newModule('xxx'), new RuntimeException("foo"))
-        def differentFailure = factory.createUnresolvedDependency(selector('requested'), fromModule, new RuntimeException("xxx"))
+        def differentRequested = factory.createUnresolvedDependency(selector('xxx'), fromModule, selectedModule, new ModuleVersionResolveException(selector('xxx'), "foo"))
+        def differentFrom = factory.createUnresolvedDependency(selector('requested'), newModule('xxx'), selectedModule, new ModuleVersionResolveException(selector('requested'), "foo"))
+        def differentFailure = factory.createUnresolvedDependency(selector('requested'), fromModule, selectedModule, new ModuleVersionResolveException(selector('requested'), "foo"))
 
         then:
         dep.is(same)
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolutionResultBuilderSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolutionResultBuilderSpec.groovy
index 9317fad..68749dd 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolutionResultBuilderSpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolutionResultBuilderSpec.groovy
@@ -23,6 +23,7 @@ import org.gradle.api.artifacts.result.*
 
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
 
 /**
  * by Szczepan Faber, created at: 8/27/12
@@ -203,7 +204,7 @@ class ResolutionResultBuilderSpec extends Specification {
         print(result.root) == """x:a:1
   x:b:1 [a]
   x:c:1 [a]
-  x:U:1 - unresolved!
+  x:U:1 -> x:U:1 - Could not resolve x:U:1.
 """
     }
 
@@ -219,8 +220,10 @@ class ResolutionResultBuilderSpec extends Specification {
     }
 
     private InternalDependencyResult dep(String requested, Exception failure = null, String selected = requested, ModuleVersionSelectionReason selectionReason = VersionSelectionReasons.REQUESTED) {
-        def selection = failure != null ? null : new DummyModuleVersionSelection(selectedId: newId("x", selected, "1"), selectionReason: selectionReason)
-        new DummyInternalDependencyResult(requested: newSelector("x", requested, "1"), selected: selection, failure: failure)
+        def selection = new DummyModuleVersionSelection(selectedId: newId("x", selected, "1"), selectionReason: selectionReason)
+        def selector = newSelector("x", requested, "1")
+        failure = failure == null ? null : new ModuleVersionResolveException(selector, failure)
+        new DummyInternalDependencyResult(requested: selector, selected: selection, failure: failure)
     }
 
     private ModuleVersionIdentifier confId(String module) {
@@ -251,7 +254,7 @@ class ResolutionResultBuilderSpec extends Specification {
         }
     }
 
-    class DummyModuleVersionSelection implements ModuleVersionSelection{
+    class DummyModuleVersionSelection implements ModuleVersionSelection {
         ModuleVersionIdentifier selectedId
         ModuleVersionSelectionReason selectionReason
     }
@@ -259,6 +262,7 @@ class ResolutionResultBuilderSpec extends Specification {
     class DummyInternalDependencyResult implements InternalDependencyResult {
         ModuleVersionSelector requested
         ModuleVersionSelection selected
-        Exception failure
+        ModuleVersionResolveException failure
+        ModuleVersionSelectionReason reason
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocatorTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocatorTest.groovy
index b7affcc..18896b8 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocatorTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocatorTest.groovy
@@ -15,15 +15,14 @@
  */
 package org.gradle.api.internal.artifacts.mvnsettings
 
-import org.gradle.util.TemporaryFolder
-
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
-import org.gradle.util.TestFile
 import spock.lang.Unroll
 
 class DefaultLocalMavenRepositoryLocatorTest extends Specification {
-    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
     SimpleMavenFileLocations locations
     DefaultLocalMavenRepositoryLocator locator
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactoryTest.groovy
index 8d01b62..d3f1c8e 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactoryTest.groovy
@@ -21,7 +21,6 @@ import org.apache.ivy.plugins.resolver.DependencyResolver
 import org.gradle.api.InvalidUserDataException
 import org.gradle.api.artifacts.dsl.RepositoryHandler
 import org.gradle.api.artifacts.repositories.ArtifactRepository
-import org.gradle.api.internal.artifacts.ArtifactPublisherFactory
 import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory
 import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder
@@ -49,14 +48,13 @@ class DefaultBaseRepositoryFactoryTest {
     final FileResolver fileResolver = context.mock(FileResolver.class)
     final RepositoryTransportFactory transportFactory = context.mock(RepositoryTransportFactory.class)
     final LocallyAvailableResourceFinder locallyAvailableResourceFinder = context.mock(LocallyAvailableResourceFinder.class)
-    final ArtifactPublisherFactory artifactPublisherFactory = context.mock(ArtifactPublisherFactory)
     final RepositoryCacheManager localCacheManager = context.mock(RepositoryCacheManager)
     final RepositoryCacheManager downloadingCacheManager = context.mock(RepositoryCacheManager)
     final ProgressLoggerFactory progressLoggerFactory = context.mock(ProgressLoggerFactory)
 
     final DefaultBaseRepositoryFactory factory = new DefaultBaseRepositoryFactory(
             localMavenRepoLocator, fileResolver, new DirectInstantiator(), transportFactory, locallyAvailableResourceFinder,
-            progressLoggerFactory, localCacheManager, downloadingCacheManager, artifactPublisherFactory
+            progressLoggerFactory, localCacheManager, downloadingCacheManager
     )
 
     @Before public void setup() {
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy
index 60f4b4a..ca49111 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy
@@ -19,18 +19,17 @@ import org.apache.ivy.core.cache.RepositoryCacheManager
 import org.gradle.api.InvalidUserDataException
 import org.gradle.api.artifacts.repositories.PasswordCredentials
 import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver
-import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder
 import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository
 import org.gradle.api.internal.externalresource.transport.file.FileTransport
 import org.gradle.api.internal.externalresource.transport.http.HttpTransport
-import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex
-import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder
 import org.gradle.api.internal.file.FileResolver
 import org.gradle.api.internal.file.TemporaryFileProvider
-import spock.lang.Specification
+import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.logging.ProgressLoggerFactory
-import org.gradle.api.internal.artifacts.ArtifactPublisherFactory
+import spock.lang.Specification
 
 class DefaultIvyArtifactRepositoryTest extends Specification {
     final FileResolver fileResolver = Mock()
@@ -40,10 +39,9 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
     final LocallyAvailableResourceFinder locallyAvailableResourceFinder = Mock()
     final CachedExternalResourceIndex cachedExternalResourceIndex = Mock()
     final ProgressLoggerFactory progressLoggerFactory = Mock()
-    final ArtifactPublisherFactory artifactPublisherFactory = Mock()
 
     final DefaultIvyArtifactRepository repository = new DefaultIvyArtifactRepository(
-            fileResolver, credentials, transportFactory, locallyAvailableResourceFinder, artifactPublisherFactory
+            fileResolver, credentials, transportFactory, locallyAvailableResourceFinder, new DirectInstantiator()
     )
 
     def "cannot create a resolver for url with unknown scheme"() {
@@ -93,11 +91,13 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         def resolver = repository.createResolver()
 
         then:
-        resolver instanceof ExternalResourceResolver
-        resolver.repository instanceof ExternalResourceRepository
-        resolver.name == 'name'
-        resolver.artifactPatterns == ['http://host/[organisation]/[artifact]-[revision].[ext]', 'http://other/[module]/[artifact]-[revision].[ext]'] as List
-        resolver.ivyPatterns == ['http://host/[module]/ivy-[revision].xml'] as List
+        with(resolver) {
+            it instanceof ExternalResourceResolver
+            repository instanceof ExternalResourceRepository
+            name == 'name'
+            artifactPatterns == ['http://host/[organisation]/[artifact]-[revision].[ext]', 'http://other/[module]/[artifact]-[revision].[ext]']
+            ivyPatterns == ['http://host/[module]/ivy-[revision].xml']
+        }
     }
 
     def "creates a resolver for file patterns"() {
@@ -116,11 +116,13 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         def resolver = repository.createResolver()
 
         then:
-        resolver instanceof ExternalResourceResolver
-        resolver.repository instanceof ExternalResourceRepository
-        resolver.name == 'name'
-        resolver.artifactPatterns == ["${file.absolutePath}/[organisation]/[artifact]-[revision].[ext]", "${file.absolutePath}/[organisation]/[module]/[artifact]-[revision].[ext]"] as List
-        resolver.ivyPatterns == ["${file.absolutePath}/[organisation]/[module]/ivy-[revision].xml"] as List
+        with(resolver) {
+            it instanceof ExternalResourceResolver
+            repository instanceof ExternalResourceRepository
+            name == 'name'
+            artifactPatterns == ["${file.absolutePath}/[organisation]/[artifact]-[revision].[ext]", "${file.absolutePath}/[organisation]/[module]/[artifact]-[revision].[ext]"]
+            ivyPatterns == ["${file.absolutePath}/[organisation]/[module]/ivy-[revision].xml"]
+        }
     }
 
     def "uses gradle patterns with specified url and default layout"() {
@@ -135,11 +137,13 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         def resolver = repository.createResolver()
 
         then:
-        resolver instanceof ExternalResourceResolver
-        resolver.repository instanceof ExternalResourceRepository
-        resolver.name == 'name'
-        resolver.artifactPatterns == ['http://host/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])'] as List
-        resolver.ivyPatterns == ["http://host/[organisation]/[module]/[revision]/ivy-[revision].xml"] as List
+        with(resolver) {
+            it instanceof ExternalResourceResolver
+            repository instanceof ExternalResourceRepository
+            name == 'name'
+            artifactPatterns == ['http://host/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])']
+            ivyPatterns == ["http://host/[organisation]/[module]/[revision]/ivy-[revision].xml"]
+        }
     }
 
     def "uses maven patterns with specified url and maven layout"() {
@@ -155,12 +159,14 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         def resolver = repository.createResolver()
 
         then:
-        resolver instanceof ExternalResourceResolver
-        resolver.repository instanceof ExternalResourceRepository
-        resolver.name == 'name'
-        resolver.artifactPatterns == ['http://host/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])'] as List
-        resolver.ivyPatterns == ["http://host/[organisation]/[module]/[revision]/ivy-[revision].xml"] as List
-        resolver.m2compatible
+        with(resolver) {
+            it instanceof ExternalResourceResolver
+            repository instanceof ExternalResourceRepository
+            name == 'name'
+            artifactPatterns == ['http://host/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])']
+            ivyPatterns == ["http://host/[organisation]/[module]/[revision]/ivy-[revision].xml"]
+            m2compatible
+        }
     }
 
     def "uses specified base url with configured pattern layout"() {
@@ -179,11 +185,41 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         def resolver = repository.createResolver()
 
         then:
-        resolver instanceof ExternalResourceResolver
-        resolver.repository instanceof ExternalResourceRepository
-        resolver.name == 'name'
-        resolver.artifactPatterns == ['http://host/[module]/[revision]/[artifact](.[ext])'] as List
-        resolver.ivyPatterns == ["http://host/[module]/[revision]/ivy.xml"] as List
+        with(resolver) {
+            it instanceof ExternalResourceResolver
+            repository instanceof ExternalResourceRepository
+            name == 'name'
+            artifactPatterns == ['http://host/[module]/[revision]/[artifact](.[ext])']
+            ivyPatterns == ["http://host/[module]/[revision]/ivy.xml"]
+            !resolver.m2compatible
+        }
+    }
+
+    def "when requested uses maven patterns with configured pattern layout"() {
+        repository.name = 'name'
+        repository.url = 'http://host'
+        repository.layout 'pattern', {
+            artifact '[module]/[revision]/[artifact](.[ext])'
+            ivy '[module]/[revision]/ivy.xml'
+            m2compatible = true
+        }
+
+        given:
+        fileResolver.resolveUri('http://host') >> new URI('http://host/')
+        transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
+
+        when:
+        def resolver = repository.createResolver()
+
+        then:
+        with(resolver) {
+            it instanceof ExternalResourceResolver
+            repository instanceof ExternalResourceRepository
+            name == 'name'
+            artifactPatterns == ['http://host/[module]/[revision]/[artifact](.[ext])']
+            ivyPatterns == ["http://host/[module]/[revision]/ivy.xml"]
+            m2compatible
+        }
     }
 
     def "combines layout patterns with additionally specified patterns"() {
@@ -200,11 +236,13 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         def resolver = repository.createResolver()
 
         then:
-        resolver instanceof ExternalResourceResolver
-        resolver.repository instanceof ExternalResourceRepository
-        resolver.name == 'name'
-        resolver.artifactPatterns == ['http://host/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])', 'http://host/[other]/artifact'] as List
-        resolver.ivyPatterns == ["http://host/[organisation]/[module]/[revision]/ivy-[revision].xml", 'http://host/[other]/ivy'] as List
+        with(resolver) {
+            it instanceof ExternalResourceResolver
+            repository instanceof ExternalResourceRepository
+            name == 'name'
+            artifactPatterns == ['http://host/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])', 'http://host/[other]/artifact']
+            ivyPatterns == ["http://host/[organisation]/[module]/[revision]/ivy-[revision].xml", 'http://host/[other]/ivy']
+        }
     }
 
     def "uses artifact pattern for ivy files when no ivy pattern provided"() {
@@ -224,7 +262,7 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         def resolver = repository.createResolver()
 
         then:
-        resolver.artifactPatterns == ['http://host/[layoutPattern]', 'http://other/[additionalPattern]'] as List
+        resolver.artifactPatterns == ['http://host/[layoutPattern]', 'http://other/[additionalPattern]']
         resolver.ivyPatterns == resolver.artifactPatterns
     }
 
@@ -243,5 +281,4 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
     private HttpTransport createHttpTransport(String name, PasswordCredentials credentials) {
         return new HttpTransport(name, credentials, cacheManager, progressLoggerFactory, Mock(TemporaryFileProvider), cachedExternalResourceIndex)
     }
-
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryCacheManagerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryCacheManagerTest.groovy
index e85f894..61bff84 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryCacheManagerTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryCacheManagerTest.groovy
@@ -26,7 +26,7 @@ import org.gradle.api.internal.externalresource.cached.CachedExternalResourceInd
 import org.gradle.api.internal.file.TemporaryFileProvider
 import org.gradle.api.internal.filestore.FileStore
 import org.gradle.api.internal.filestore.FileStoreEntry
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -43,7 +43,7 @@ class DownloadingRepositoryCacheManagerTest extends Specification {
     FileStoreEntry fileStoreEntry = Mock()
     DownloadingRepositoryCacheManager downloadingRepositoryCacheManager = new DownloadingRepositoryCacheManager("TestCacheManager", fileStore, artifactUrlCachedResolutionIndex, tmpFileProvider, lockingManager)
 
-    @Rule TemporaryFolder temporaryFolder;
+    @Rule TestNameTestDirectoryProvider temporaryFolder;
 
     void "downloadArtifactFile downloads artifact to temporary file and then moves it into the file store"() {
         setup:
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolverTest.groovy
new file mode 100644
index 0000000..b2211cf
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolverTest.groovy
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.repositories.resolver
+
+import org.apache.ivy.core.module.descriptor.Artifact
+import org.apache.ivy.core.module.id.ArtifactRevisionId
+import org.apache.ivy.core.module.id.ModuleId
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.apache.ivy.core.report.ArtifactDownloadReport
+import org.apache.ivy.core.report.DownloadStatus
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult
+import org.gradle.api.internal.artifacts.repositories.cachemanager.EnhancedArtifactDownloadReport
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder
+import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository
+import spock.lang.Specification
+
+class ExternalResourceResolverTest extends Specification {
+    String name = "TestResolver"
+    ExternalResourceRepository repository = Mock()
+    VersionLister versionLister = Mock()
+    LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder = Mock()
+    BuildableArtifactResolveResult result = Mock()
+    Artifact artifact = Mock()
+    MavenResolver.TimestampedModuleSource moduleSource = Mock()
+    EnhancedArtifactDownloadReport downloadReport = Mock()
+
+    ExternalResourceResolver resolver
+    ModuleRevisionId artifactModuleRevisionId = Mock()
+
+    def setup() {
+        //We use a spy here to avoid dealing with all the overhead ivys basicresolver brings in here.
+        resolver = Spy(ExternalResourceResolver, constructorArgs: [name, repository, versionLister, locallyAvailableResourceFinder])
+        resolver.download(_) >> downloadReport
+    }
+
+    def reportsNotFoundArtifactResolveResult() {
+        given:
+        artifactIsMissing()
+
+        when:
+        resolver.resolve(artifact, result, moduleSource)
+
+        then:
+        1 * result.notFound(artifact)
+        0 * result._
+    }
+
+    def reportsFailedArtifactResolveResult() {
+        given:
+        downloadIsFailing()
+
+        when:
+        resolver.resolve(artifact, result, moduleSource)
+
+        then:
+        1 * result.failed(_) >> { exception ->
+            assert exception.message.toString() == "[Could not download artifact 'group:projectA:1.0 at jar']"
+        }
+        0 * result._
+    }
+
+    def reportsResolvedArtifactResolveResult() {
+        given:
+        artifactCanBeResolved()
+
+        when:
+        resolver.resolve(artifact, result, moduleSource)
+
+        then:
+        1 * result.resolved(_)
+        0 * result._
+    }
+
+    def reportsResolvedArtifactResolveResultWithSnapshotVersion() {
+        given:
+        artifactIsTimestampedSnapshotVersion()
+        artifactCanBeResolved()
+
+        when:
+        resolver.resolve(artifact, result, moduleSource)
+
+        then:
+        1 * result.resolved(_)
+        0 * result._
+    }
+
+    def artifactIsTimestampedSnapshotVersion() {
+        _ * moduleSource.timestampedVersion >> "1.0-20100101.120001-1"
+        _ * artifact.getModuleRevisionId() >> artifactModuleRevisionId
+        _ * artifactModuleRevisionId.organisation >> "group"
+        _ * artifactModuleRevisionId.name >> "projectA"
+        _ * artifactModuleRevisionId.revision >> "1.0"
+        ModuleId moduleId = Mock()
+        _ * moduleId.equals(_) >> true
+        _ * artifactModuleRevisionId.moduleId >> moduleId
+        _ * artifactModuleRevisionId.qualifiedExtraAttributes >> [:]
+    }
+
+    def artifactIsMissing() {
+        1 * downloadReport.getDownloadStatus() >> DownloadStatus.FAILED
+        1 * downloadReport.getDownloadDetails() >> ArtifactDownloadReport.MISSING_ARTIFACT;
+    }
+
+    def downloadIsFailing() {
+        1 * downloadReport.getDownloadStatus() >> DownloadStatus.FAILED
+        1 * downloadReport.getDownloadDetails() >> "Broken Connection";
+        1 * downloadReport.getArtifact() >> artifact
+        1 * artifact.getModuleRevisionId() >> {
+            ModuleRevisionId moduleRevisionId = Mock()
+            1 * moduleRevisionId.organisation >> "group"
+            1 * moduleRevisionId.name >> "projectA"
+            1 * moduleRevisionId.revision >> "1.0"
+            1 * artifact.ext >> "jar"
+            moduleRevisionId
+        };
+    }
+
+
+    def artifactCanBeResolved() {
+        1 * downloadReport.getDownloadStatus() >> DownloadStatus.SUCCESSFUL
+        1 * downloadReport.getLocalFile() >> Mock(File);
+
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/M2ResourcePatternTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/M2ResourcePatternTest.groovy
index e3f0b53..7969433 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/M2ResourcePatternTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/M2ResourcePatternTest.groovy
@@ -67,6 +67,15 @@ class M2ResourcePatternTest extends Specification {
         pattern.toModuleVersionPath(artifact2) == 'prefix/org/group/projectA/1.2'
     }
 
+    def "can substitute revision in filename by timestamped version when provided"() {
+        def pattern = new M2ResourcePattern("prefix/" + MavenPattern.M2_PATTERN)
+        def artifact1 = new DefaultArtifact(ModuleRevisionId.newInstance("group", "projectA", "1.2-SNAPSHOT",  [timestamp: "1.2-20100101.120001-1"]), new Date(), "projectA", "ivy", "ivy")
+        def artifact2 = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("org.group", "projectA", "1.2-SNAPSHOT"), new Date())
+        expect:
+        pattern.toPath(artifact1) == 'prefix/group/projectA/1.2-SNAPSHOT/projectA-1.2-20100101.120001-1.ivy'
+        pattern.toPath(artifact2) == 'prefix/org/group/projectA/1.2-SNAPSHOT/ivy-1.2-SNAPSHOT.xml'
+    }
+
     def "throws UnsupportedOperationException for non M2 compatible pattern"() {
         def pattern = new M2ResourcePattern("/non/m2/pattern")
 
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/resolutioncache/DefaultArtifactResolutionCacheTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/resolutioncache/DefaultArtifactResolutionCacheTest.groovy
index 0fa2ad5..f352560 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/resolutioncache/DefaultArtifactResolutionCacheTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/resolutioncache/DefaultArtifactResolutionCacheTest.groovy
@@ -18,20 +18,20 @@ package org.gradle.api.internal.artifacts.resolutioncache
 
 import org.gradle.CacheUsage
 import org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager
+import org.gradle.api.internal.externalresource.cached.DefaultCachedExternalResourceIndex
+import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData
 import org.gradle.cache.internal.CacheFactory
 import org.gradle.cache.internal.DefaultCacheRepository
-import org.gradle.testfixtures.internal.InMemoryCacheFactory
-import org.gradle.util.TemporaryFolder
 import org.gradle.internal.TimeProvider
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.testfixtures.internal.InMemoryCacheFactory
 import org.junit.Rule
 import spock.lang.Specification
 import spock.lang.Unroll
-import org.gradle.api.internal.externalresource.cached.DefaultCachedExternalResourceIndex
-import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData
 
 class DefaultArtifactResolutionCacheTest extends Specification {
 
-    @Rule TemporaryFolder tmp = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider tmp = new TestNameTestDirectoryProvider()
 
     Date time = new Date(0)
     TimeProvider timeProvider = new TimeProvider() {
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/CachedExternalResourceAdapterTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/CachedExternalResourceAdapterTest.groovy
index b63ed1b..282e36b 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/CachedExternalResourceAdapterTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/CachedExternalResourceAdapterTest.groovy
@@ -13,20 +13,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.api.internal.externalresource;
+package org.gradle.api.internal.externalresource
 
+import org.gradle.api.internal.externalresource.cached.CachedExternalResource
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceAdapter
+import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData
 import org.gradle.api.internal.externalresource.transfer.ExternalResourceAccessor
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.hash.HashUtil
 import org.gradle.util.hash.HashValue
 import org.junit.Rule
 import spock.lang.Specification
-import org.gradle.api.internal.externalresource.cached.CachedExternalResourceAdapter
-import org.gradle.api.internal.externalresource.cached.CachedExternalResource
-import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData
 
 public class CachedExternalResourceAdapterTest extends Specification {
-    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
     ExternalResourceAccessor accessor = Mock()
     CachedExternalResource cachedExternalResource = Mock()
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedArtifactIndexTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedArtifactIndexTest.groovy
new file mode 100644
index 0000000..7af9d1b
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedArtifactIndexTest.groovy
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.externalresource.ivy
+
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.CachingModuleVersionRepository
+import org.gradle.api.internal.externalresource.cached.CachedArtifact
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData
+import org.gradle.cache.PersistentIndexedCache
+import org.gradle.internal.TimeProvider
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+class ArtifactAtRepositoryCachedArtifactIndexTest extends Specification {
+
+    CacheLockingManager cacheLockingManager = Mock()
+    TimeProvider timeProvider = Mock()
+    ArtifactAtRepositoryKey key = Mock()
+    ExternalResourceMetaData metaData = Mock()
+    CachingModuleVersionRepository moduleVersionRepository = Mock()
+
+    @Rule TestNameTestDirectoryProvider folder = new TestNameTestDirectoryProvider();
+
+    PersistentIndexedCache persistentIndexedCache = Mock()
+
+    ArtifactAtRepositoryCachedArtifactIndex index
+    File persistentCacheFile
+    CachedArtifact cachedArtifact = Mock()
+
+
+    def setup() {
+        persistentCacheFile = folder.createFile("cacheFile")
+        index = new ArtifactAtRepositoryCachedArtifactIndex(persistentCacheFile, timeProvider, cacheLockingManager)
+    }
+
+    def "storing null artifactFile not supported"() {
+        when:
+        index.store(key, null, 0)
+
+        then:
+        def e = thrown(IllegalArgumentException)
+        e.message == "artifactFile cannot be null"
+    }
+
+    def "artifact key must be provided"() {
+        when:
+        index.store(null, Mock(File), 0)
+        then:
+        def e = thrown(IllegalArgumentException)
+        e.message == "key cannot be null"
+    }
+
+    def "stored artifact is put into persistentIndexedCache"() {
+        setup:
+        1 * moduleVersionRepository.getId() >> "RepoID"
+        1 * cacheLockingManager.createCache(persistentCacheFile, _, _) >> persistentIndexedCache
+        def key = new ArtifactAtRepositoryKey(moduleVersionRepository, "artifactId");
+        def testFile = folder.createFile("aTestFile");
+        when:
+        index.store(key, testFile, BigInteger.TEN)
+
+        then:
+        1 * cacheLockingManager.useCache("store into artifact resolution cache \'cacheFile\'", _) >> {descr, action -> action.run()}
+        1 * timeProvider.currentTime >> 123
+        1 * persistentIndexedCache.put(key, _) >> { k, v ->
+            assert v.cachedAt == 123
+            assert v.descriptorHash == BigInteger.TEN
+            assert v.cachedFile == testFile
+        }
+    }
+
+    def "lookup key must not be null"() {
+        when:
+        index.lookup(null)
+        then:
+        def e = thrown(IllegalArgumentException)
+        e.message == "key cannot be null"
+    }
+
+    def "loads missing CachedArtifact from persistentIndexedCache"() {
+        setup:
+        def key = createEntryInPersistentCache()
+        _ * cachedArtifact.missing >> true
+
+        when:
+        def fromCache = index.lookup(key)
+
+        then:
+        fromCache == cachedArtifact
+        fromCache.isMissing()
+    }
+
+    def "loads CachedArtifact with file ref from persistentIndexedCache"() {
+        setup:
+        def key = createEntryInPersistentCache()
+        _ * cachedArtifact.missing >> false
+        File cachedFile = Mock()
+        cachedFile.exists() >> true;
+        cachedArtifact.getCachedFile() >> cachedFile
+        when:
+        def fromCache = index.lookup(key)
+
+        then:
+        fromCache == cachedArtifact
+        !fromCache.isMissing()
+        fromCache.cachedFile == cachedArtifact.cachedFile
+    }
+
+    def createEntryInPersistentCache() {
+        1 * moduleVersionRepository.getId() >> "RepoID"
+        1 * cacheLockingManager.createCache(persistentCacheFile, _, _) >> persistentIndexedCache
+        1 * cacheLockingManager.useCache("lookup from artifact resolution cache \'cacheFile\'", _) >> {descr, factory -> factory.create()}
+        def key = new ArtifactAtRepositoryKey(moduleVersionRepository, "artifactId");
+        1 * persistentIndexedCache.get(key) >> cachedArtifact;
+        key
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/DefaultLocallyAvailableResourceTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/DefaultLocallyAvailableResourceTest.groovy
index 0dfabf3..3db4adc 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/DefaultLocallyAvailableResourceTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/DefaultLocallyAvailableResourceTest.groovy
@@ -15,13 +15,13 @@
  */
 package org.gradle.api.internal.externalresource.local
 
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.hash.HashUtil
 import org.junit.Rule
 import spock.lang.Specification
-import org.gradle.util.hash.HashUtil
 
 public class DefaultLocallyAvailableResourceTest extends Specification {
-    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
     def "uses value from origin file"() {
         given:
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/LazyLocallyAvailableResourceCandidatesTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/LazyLocallyAvailableResourceCandidatesTest.groovy
index 3ad9df9..3536f60 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/LazyLocallyAvailableResourceCandidatesTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/LazyLocallyAvailableResourceCandidatesTest.groovy
@@ -17,14 +17,14 @@
 package org.gradle.api.internal.externalresource.local
 
 import org.gradle.internal.Factory
-import spock.lang.Specification
-import org.gradle.util.TemporaryFolder
-import org.junit.Rule
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.hash.HashUtil
+import org.junit.Rule
+import spock.lang.Specification
 
 class LazyLocallyAvailableResourceCandidatesTest extends Specification {
 
-    @Rule TemporaryFolder tmp
+    @Rule TestNameTestDirectoryProvider tmp
     
     def "does not query factory until necessary"() {
         given:
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientHelperTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientHelperTest.groovy
index 49f4f16..e4e9fe5 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientHelperTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientHelperTest.groovy
@@ -15,21 +15,19 @@
  */
 
 package org.gradle.api.internal.externalresource.transport.http
-
 import org.apache.http.HttpResponse
+import org.apache.http.client.methods.HttpGet
 import org.apache.http.client.methods.HttpRequestBase
 import org.gradle.api.artifacts.repositories.PasswordCredentials
-import org.apache.http.client.methods.HttpGet
-
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
 import spock.lang.Specification
 
 class HttpClientHelperTest extends Specification {
+    @Rule SetSystemProperties sysProp = new SetSystemProperties()
+
     def "throws HttpRequestException if an IO error occurs during a request"() {
-        def settings = Stub(HttpSettings) {
-            getCredentials() >> Stub(PasswordCredentials)
-            getProxySettings() >> Stub(HttpProxySettings)
-        }
-        def client = new HttpClientHelper(settings) {
+        def client = new HttpClientHelper(httpSettings) {
             @Override
             protected HttpResponse executeGetOrHead(HttpRequestBase method) {
                 throw new IOException("ouch")
@@ -43,4 +41,22 @@ class HttpClientHelperTest extends Specification {
         HttpRequestException e = thrown()
         e.cause.message == "ouch"
     }
+
+    def "always sets http.keepAlive system property to 'true'"() {
+        given:
+        System.setProperty("http.keepAlive", "false")
+
+        when:
+        new HttpClientHelper(httpSettings)
+
+        then:
+        System.getProperty("http.keepAlive", "true")
+    }
+
+    private HttpSettings getHttpSettings() {
+        return Stub(HttpSettings) {
+            getCredentials() >> Stub(PasswordCredentials)
+            getProxySettings() >> Stub(HttpProxySettings)
+        }
+    }
 }
diff --git a/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-bad-confs.xml b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-bad-confs.xml
new file mode 100644
index 0000000..7287882
--- /dev/null
+++ b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-bad-confs.xml
@@ -0,0 +1,27 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivy-module version="1.0">
+	<info  organisation="myorg"
+	       module="mymodule"
+	       status="integration"
+	/>
+	<configurations>
+		<conf name="A" extends="invalidConf"/>
+	</configurations>
+</ivy-module>
diff --git a/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-cyclic-confs1.xml b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-cyclic-confs1.xml
new file mode 100644
index 0000000..3890369
--- /dev/null
+++ b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-cyclic-confs1.xml
@@ -0,0 +1,28 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivy-module version="1.0">
+	<info  organisation="myorg"
+	       module="mymodule"
+	       status="integration"
+	/>
+	<configurations>
+		<conf name="A" extends="B"/>
+		<conf name="B" extends="A"/>
+	</configurations>
+</ivy-module>
diff --git a/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-empty-dependencies.xml b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-empty-dependencies.xml
new file mode 100644
index 0000000..3240dc4
--- /dev/null
+++ b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-empty-dependencies.xml
@@ -0,0 +1,28 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivy-module version="1.0">
+	<info organisation="myorg"
+	       module="mymodule"
+	       revision="myrev"
+	       status="integration"
+	       publication="20041101110000"
+	/>
+	<dependencies>
+	</dependencies>
+</ivy-module>
diff --git a/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-full.xml b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-full.xml
new file mode 100644
index 0000000..dfb4a55
--- /dev/null
+++ b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/test-full.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.    
+-->
+<ivy-module version="2.0" xmlns:e="http://ant.apache.org/ivy/extra">
+	<info organisation="myorg"
+	       module="mymodule"
+	       revision="myrev"
+	       status="integration"
+	       publication="20041101110000"
+	       e:attr1="value1">
+	       
+		<license name="MyLicense" url="http://www.my.org/mymodule/mylicense.html"/>
+		<repository name="ivyrep" url="http://www.jayasoft.fr/org/ivyrep/" pattern="[organisation]/[module]/ivy-[revision].xml" ivys="true" artifacts="false"/>
+		
+		<ivyauthor name="jayasoft" url="http://www.jayasoft.org/"/>
+		<ivyauthor name="myorg" url="http://www.myorg.org/"/>
+
+		
+		<description homepage="http://www.my.org/mymodule/">			
+	This module is <b>great</b> !<br/>
+	You can use it especially with myconf1 and myconf2, and myconf4 is not too bad too.
+		</description>
+
+		<e:someExtra>56576</e:someExtra>
+	</info>
+	<configurations>
+		<conf name="myconf1" description="desc 1" e:attr2="value2"/>
+		<conf name="myconf2" description="desc 2" visibility="public"/>
+		<conf name="myconf3" description="desc 3" visibility="private"/>
+		<conf name="myconf4" description="desc 4" extends="myconf1, myconf2"/>		
+		<conf name="myoldconf" description="my old desc" deprecated="20050115"/>
+	</configurations>
+	<publications>
+		<artifact name="myartifact1" type="jar" e:attr3="value3"/>
+		<artifact name="myartifact2" type="jar" conf="myconf1"/>
+		<artifact name="myartifact3" type="jar" conf="myconf1, myconf2, myconf3"/>
+		<artifact name="myartifact4" type="jar">
+			<conf name="myconf1"/>
+			<conf name="myconf3"/>
+		</artifact>
+	</publications>
+	<dependencies>
+		<dependency name="mymodule2" rev="2.0" e:attr4="value4"/>
+		<dependency name="mymodule3" rev="2.0" changing="true" transitive="false"/>
+		<dependency org="yourorg" name="yourmodule1" branch="trunk" rev="1.1" branchConstraint="branch1" revConstraint="1+" conf="myconf1"/>
+		<dependency org="yourorg" name="yourmodule2" rev="2+" conf="myconf1->yourconf1"/>
+		<dependency org="yourorg" name="yourmodule3" rev="3.1" conf="myconf1->yourconf1, yourconf2"/>
+		<dependency org="yourorg" name="yourmodule4" rev="4.1" conf="myconf1, myconf2->yourconf1, yourconf2"/>
+		<dependency org="yourorg" name="yourmodule5" rev="5.1" conf="myconf1->yourconf1;myconf2->yourconf1, yourconf2"/>
+
+		<dependency org="yourorg" name="yourmodule6" rev="latest.integration">
+			<conf name="myconf1" mapped="yourconf1"/>
+			<conf name="myconf2" mapped="yourconf1, yourconf2"/>
+		</dependency>
+
+		<dependency org="yourorg" name="yourmodule7" rev="7.1">
+			<conf name="myconf1">
+				<mapped name="yourconf1"/>
+			</conf>
+			<conf name="myconf2">
+				<mapped name="yourconf1"/>
+				<mapped name="yourconf2"/>
+			</conf>
+		</dependency>
+
+		<dependency org="yourorg" name="yourmodule8" rev="8.1">
+			<artifact name="yourartifact8-1" type="jar" e:attr5="value5"/>
+			<artifact name="yourartifact8-2" type="jar"/>
+		</dependency>		
+
+		<dependency org="yourorg" name="yourmodule9" rev="9.1" conf="myconf1,myconf2,myconf3->default">
+			<artifact name="yourartifact9-1" type="jar" conf="myconf1,myconf2"/>
+			<artifact name="yourartifact9-2" type="jar">
+				<conf name="myconf2"/>
+				<conf name="myconf3"/>
+			</artifact>
+		</dependency>		
+
+		<dependency org="yourorg" name="yourmodule10" rev="10.1">
+			<include name="your.*" type="jar"/>
+			<include ext="xml"/>
+			<exclude name="toexclude"/>
+		</dependency>
+		<dependency org="yourorg" name="yourmodule11" rev="11.1" conf="*->@"/>
+		
+		<exclude module="*servlet*" matcher="glob" conf="myconf1" /> 
+		<exclude org="acme" module="test" artifact="test" type="source" ext="jar" />
+        <override org="yourorg" module=".*1" matcher="regexp" branch="BRANCH" rev="1.0" /> 
+		<conflict org="yourorg" module=".*" matcher="regexp" manager="all" />
+		<conflict org="theirorg" module="theirmodule1" rev="1.0, 1.1"/>
+	</dependencies>
+</ivy-module>
diff --git a/subprojects/core-impl/src/testFixtures/groovy/org/gradle/api/internal/artifacts/result/ResolutionResultDataBuilder.groovy b/subprojects/core-impl/src/testFixtures/groovy/org/gradle/api/internal/artifacts/result/ResolutionResultDataBuilder.groovy
index cded200..da4afe7 100644
--- a/subprojects/core-impl/src/testFixtures/groovy/org/gradle/api/internal/artifacts/result/ResolutionResultDataBuilder.groovy
+++ b/subprojects/core-impl/src/testFixtures/groovy/org/gradle/api/internal/artifacts/result/ResolutionResultDataBuilder.groovy
@@ -21,6 +21,7 @@ import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.Version
 
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
 
 /**
  * by Szczepan Faber, created at: 10/2/12
@@ -31,8 +32,9 @@ class ResolutionResultDataBuilder {
         new DefaultResolvedDependencyResult(newSelector(group, module, version), newModule(group, module, selectedVersion), newModule())
     }
 
-    static DefaultUnresolvedDependencyResult newUnresolvedDependency(String group='x', String module='x', String version='1') {
-        new DefaultUnresolvedDependencyResult(newSelector(group, module, version), new RuntimeException("boo!"), newModule())
+    static DefaultUnresolvedDependencyResult newUnresolvedDependency(String group='x', String module='x', String version='1', String selectedVersion='1') {
+        def requested = newSelector(group, module, version)
+        new DefaultUnresolvedDependencyResult(requested, VersionSelectionReasons.REQUESTED, newModule(group, module, selectedVersion), new ModuleVersionResolveException(requested, "broken"))
     }
 
     static DefaultResolvedModuleVersionResult newModule(String group='a', String module='a', String version='1',
diff --git a/subprojects/core/core.gradle b/subprojects/core/core.gradle
index 71947b8..53b252e 100755
--- a/subprojects/core/core.gradle
+++ b/subprojects/core/core.gradle
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+apply from: "$rootDir/gradle/providedConfiguration.gradle"
+
 configurations {
     reports
 }
@@ -49,12 +51,14 @@ dependencies {
     testCompile libraries.log4j_to_slf4j
     testCompile libraries.jcl_to_slf4j
 
-    testRuntime "xerces:xercesImpl:2.9.1"
+    testRuntime libraries.xerces
     testRuntime project(":diagnostics")
 
     testFixturesCompile project(":internalTesting")
     testFixturesRuntime project(':coreImpl')
 
+    testCompile project(':coreImpl')
+
     integTestCompile project(":internalIntegTesting")
 
     reports 'css3-pie:css3-pie:1.0beta3'
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/ConfigurationOnDemandIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/ConfigurationOnDemandIntegrationTest.groovy
new file mode 100644
index 0000000..a4a081f
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/ConfigurationOnDemandIntegrationTest.groovy
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.api
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+/**
+ * by Szczepan Faber, created at: 11/21/12
+ */
+class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
+
+    def setup() {
+        file("gradle.properties") << "systemProp.org.gradle.configuration.ondemand=true"
+        alwaysUsing { it.withArgument('-i') }
+    }
+
+    def "works with single-module project"() {
+        buildFile << "task foo"
+        when:
+        run("foo")
+        then:
+        result.assertProjectsEvaluated(":")
+    }
+
+    def "evaluates only project referenced in the task list"() {
+        settingsFile << "include 'api', 'impl', 'util', 'util:impl'"
+        buildFile << "allprojects { task foo }"
+
+        when:
+        run(":foo", ":util:impl:foo")
+
+        then:
+        result.assertProjectsEvaluated(":", ":util:impl")
+    }
+
+    def "follows java project dependencies"() {
+        settingsFile << "include 'api', 'impl', 'util'"
+        buildFile << "allprojects { apply plugin: 'java' } "
+
+        file("impl/build.gradle") << "dependencies { compile project(':api') } "
+        file("util/build.gradle") << "dependencies { compile project(':impl') } "
+        //util -> impl -> api
+
+        file("api/src/main/java/Person.java") << """public interface Person {
+    String getName();
+}
+"""
+        file("impl/src/main/java/PersonImpl.java") << """public class PersonImpl implements Person {
+    public String getName() {
+        return "Szczepan";
+    }
+}
+"""
+        file("util/src/main/java/Utility.java") << "public class Utility extends PersonImpl {}"
+
+        when:
+        run(":api:build")
+
+        then:
+        result.assertProjectsEvaluated(":", ":api")
+
+        when:
+        run(":impl:build")
+
+        then:
+        result.assertProjectsEvaluated(":", ":impl", ":api")
+
+        when:
+        run(":util:build")
+
+        then:
+        result.assertProjectsEvaluated(":", ":util", ":impl", ":api")
+    }
+
+    def "follows project dependencies when ran in subproject"() {
+        settingsFile << "include 'api', 'impl', 'util'"
+
+        file("api/build.gradle") << "configurations { api }"
+        file("impl/build.gradle") << """
+            configurations { util }
+            dependencies { util project(path: ':api', configuration: 'api') }
+            task build(dependsOn: configurations.util)
+        """
+
+        when:
+        inDirectory("impl")
+        run("build")
+
+        then:
+        result.assertProjectsEvaluated(':', ':impl', ':api')
+    }
+
+    def "name matching execution from root evaluates all projects"() {
+        settingsFile << "include 'api', 'impl'"
+        buildFile << "task foo"
+
+        when:
+        run("foo")
+
+        then:
+        result.assertProjectsEvaluated(":", ":api", ":impl")
+
+        when:
+        run(":foo")
+
+        then:
+        result.assertProjectsEvaluated(":")
+    }
+
+    def "name matching execution from subproject evaluates only the subproject recursively"() {
+        settingsFile << "include 'api', 'impl:one', 'impl:two', 'impl:two:abc'"
+        file("impl/build.gradle") << "task foo"
+
+        when:
+        inDirectory("impl")
+        run("foo")
+
+        then:
+        result.assertProjectsEvaluated(":", ":impl", ":impl:one", ":impl:two", ":impl:two:abc")
+    }
+
+    def "may run implicit tasks from root"() {
+        settingsFile << "include 'api', 'impl'"
+
+        when:
+        run(":tasks")
+
+        then:
+        result.assertProjectsEvaluated(":")
+    }
+
+    def "may run implicit tasks for subproject"() {
+        settingsFile << "include 'api', 'impl'"
+
+        when:
+        run(":api:tasks")
+
+        then:
+        result.assertProjectsEvaluated(":", ":api")
+    }
+
+    def "works with default tasks"() {
+        settingsFile << "include 'api', 'impl'"
+        file("api/build.gradle") << """
+            task foo
+            defaultTasks 'foo'
+        """
+
+        when:
+        inDirectory('api')
+        run()
+
+        then:
+        result.assertProjectsEvaluated(":", ":api")
+        result.assertTasksExecuted(':api:foo')
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/artifacts/DependencyResolutionEventsIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/artifacts/DependencyResolutionEventsIntegrationTest.groovy
deleted file mode 100644
index 4b824fa..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/artifacts/DependencyResolutionEventsIntegrationTest.groovy
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.artifacts
-
-import org.gradle.integtests.fixtures.*
-import org.gradle.integtests.fixtures.internal.*
-import spock.lang.Issue
-
-class DependencyResolutionEventsIntegrationTest extends AbstractIntegrationSpec {
-
-    @Issue("http://issues.gradle.org/browse/GRADLE-2047")
-    def "can access resolved files from afterResolve hook"() {
-        given:
-        file("thing.txt") << "stuff"
-        buildFile << """
-            configurations {
-                things.incoming.afterResolve { incoming ->
-                    incoming.files.files
-                    println "accessed files"
-                }
-            }
-            dependencies {
-                things files("thing.txt")
-            }
-
-            task resolveIt(type: Copy, dependsOn: configurations.things) {
-                from configurations.things
-                into buildDir
-            }
-        """
-
-        when:
-        run "resolveIt"
-
-        then:
-        output.contains "accessed files"
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/dsl/DynamicObjectIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/dsl/DynamicObjectIntegrationTest.groovy
index be51d9f..ee9788c 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/dsl/DynamicObjectIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/dsl/DynamicObjectIntegrationTest.groovy
@@ -15,26 +15,22 @@
  */
 package org.gradle.api.dsl
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Test
 import spock.lang.Issue
 
-class DynamicObjectIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class DynamicObjectIntegrationTest extends AbstractIntegrationTest {
 
     TestFile getBuildFile() {
-        dist.testDir.file("build.gradle")
+        file("build.gradle")
     }
 
     @Test
     public void canAddDynamicPropertiesToProject() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file("settings.gradle").writelns("include 'child'");
-        testDir.file("build.gradle").writelns(
+        
+        file("settings.gradle").writelns("include 'child'");
+        file("build.gradle").writelns(
                 "ext.rootProperty = 'root'",
                 "ext.sharedProperty = 'ignore me'",
                 "ext.property = 'value'",
@@ -43,7 +39,7 @@ class DynamicObjectIntegrationTest {
                 "task testTask",
                 "class ConventionBean { def getConventionProperty() { 'convention' } }"
         );
-        testDir.file("child/build.gradle").writelns(
+        file("child/build.gradle").writelns(
                 "ext.childProperty = 'child'",
                 "ext.sharedProperty = 'shared'",
                 "task testTask << {",
@@ -73,14 +69,14 @@ class DynamicObjectIntegrationTest {
                 "}"
         );
 
-        executer.inDirectory(testDir).withTasks("testTask").run();
+        executer.withTasks("testTask").run();
     }
 
     @Test
     public void canAddDynamicMethodsToProject() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file("settings.gradle").writelns("include 'child'");
-        testDir.file("build.gradle").writelns(
+        
+        file("settings.gradle").writelns("include 'child'");
+        file("build.gradle").writelns(
                 "def rootMethod(p) { 'root' + p }",
                 "def sharedMethod(p) { 'ignore me' }",
                 "convention.plugins.test = new ConventionBean()",
@@ -88,7 +84,7 @@ class DynamicObjectIntegrationTest {
                 "task testTask",
                 "class ConventionBean { def conventionMethod(name) { 'convention' + name } }"
         );
-        testDir.file("child/build.gradle").writelns(
+        file("child/build.gradle").writelns(
                 "def childMethod(p) { 'child' + p }",
                 "def sharedMethod(p) { 'shared' + p }",
                 "task testTask << {",
@@ -107,13 +103,13 @@ class DynamicObjectIntegrationTest {
                 "}"
         );
 
-        executer.inDirectory(testDir).withTasks("testTask").run();
+        executer.withTasks("testTask").run();
     }
 
     @Test
     public void canAddMixinsToProject() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('build.gradle') << '''
+        
+        file('build.gradle') << '''
 convention.plugins.test = new ConventionBean()
 
 assert conventionProperty == 'convention'
@@ -125,13 +121,13 @@ class ConventionBean {
 }
 '''
 
-        executer.inDirectory(testDir).run();
+        executer.run();
     }
 
     @Test
     public void canAddExtensionsToProject() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('build.gradle') << '''
+        
+        file('build.gradle') << '''
 extensions.test = new ExtensionBean()
 
 assert test instanceof ExtensionBean
@@ -142,17 +138,17 @@ class ExtensionBean {
 }
 '''
 
-        executer.inDirectory(testDir).run();
+        executer.run();
     }
 
     @Test
     public void canAddPropertiesToProjectUsingGradlePropertiesFile() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file("settings.gradle").writelns("include 'child'");
-        testDir.file("gradle.properties") << '''
+        
+        file("settings.gradle").writelns("include 'child'");
+        file("gradle.properties") << '''
 global=some value
 '''
-        testDir.file("build.gradle") << '''
+        file("build.gradle") << '''
 assert 'some value' == global
 assert hasProperty('global')
 assert 'some value' == property('global')
@@ -162,20 +158,20 @@ assert project.hasProperty('global')
 assert 'some value' == project.property('global')
 assert 'some value' == project.properties.global
 '''
-        testDir.file("child/gradle.properties") << '''
+        file("child/gradle.properties") << '''
 global=overridden value
 '''
-        testDir.file("child/build.gradle") << '''
+        file("child/build.gradle") << '''
 assert 'overridden value' == global
 '''
 
-        executer.inDirectory(testDir).run();
+        executer.run();
     }
 
     @Test
     public void canAddDynamicPropertiesToCoreDomainObjects() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('build.gradle') << '''
+        
+        file('build.gradle') << '''
             class GroovyTask extends DefaultTask { }
 
             task defaultTask {
@@ -231,13 +227,13 @@ assert 'overridden value' == global
             }
 '''
 
-        executer.inDirectory(testDir).withTasks("defaultTask").run();
+        executer.withTasks("defaultTask").run();
     }
 
     @Test
     public void canAddMixInsToCoreDomainObjects() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('build.gradle') << '''
+        
+        file('build.gradle') << '''
             class Extension { def doStuff() { 'method' } }
             class GroovyTask extends DefaultTask { }
 
@@ -285,13 +281,13 @@ assert 'overridden value' == global
             }
 '''
 
-        executer.inDirectory(testDir).withTasks("defaultTask").run();
+        executer.withTasks("defaultTask").run();
     }
 
     @Test
     public void canAddExtensionsToCoreDomainObjects() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('build.gradle') << '''
+        
+        file('build.gradle') << '''
             class Extension { def doStuff() { 'method' } }
             class GroovyTask extends DefaultTask { }
 
@@ -339,13 +335,13 @@ assert 'overridden value' == global
             }
 '''
 
-        executer.inDirectory(testDir).withTasks("defaultTask").run();
+        executer.withTasks("defaultTask").run();
     }
 
     @Test
     public void mixesDslMethodsIntoCoreDomainObjects() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('build.gradle') << '''
+        
+        file('build.gradle') << '''
             class GroovyTask extends DefaultTask {
                 def String prop
                 void doStuff(Action<Task> action) { action.execute(this) }
@@ -365,13 +361,13 @@ assert 'overridden value' == global
             assert test.prop == 'new value'
 '''
 
-        executer.inDirectory(testDir).withTasks("test").run();
+        executer.withTasks("test").run();
     }
 
     @Test
     void canAddExtensionsToDynamicExtensions() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('build.gradle') << '''
+        
+        file('build.gradle') << '''
             class Extension {
                 String name
                 Extension(String name) {
@@ -390,30 +386,30 @@ assert 'overridden value' == global
             }
         '''
 
-        executer.inDirectory(testDir).withTasks("test").run();
+        executer.withTasks("test").run();
     }
 
     @Test
     public void canInjectMethodsFromParentProject() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file("settings.gradle").writelns("include 'child'");
-        testDir.file("build.gradle").writelns(
+        
+        file("settings.gradle").writelns("include 'child'");
+        file("build.gradle").writelns(
                 "subprojects {",
                 "  ext.injectedMethod = { project.name }",
                 "}"
         );
-        testDir.file("child/build.gradle").writelns(
+        file("child/build.gradle").writelns(
                 "task testTask << {",
                 "   assert injectedMethod() == 'child'",
                 "}"
         );
 
-        executer.inDirectory(testDir).withTasks("testTask").run();
+        executer.withTasks("testTask").run();
     }
     
     @Test void canAddNewPropertiesViaTheAdhocNamespace() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file("build.gradle") << """
+        
+        file("build.gradle") << """
             assert !hasProperty("p1")
 
             ext {
@@ -452,8 +448,8 @@ assert 'overridden value' == global
     }
 
     @Test void warnsWhenNewPropertiesAreAddedDirectlyOnTargetObject() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file("build.gradle") << """
+        
+        file("build.gradle") << """
             assert !hasProperty("p1")
 
             p1 = 1
@@ -479,8 +475,8 @@ assert 'overridden value' == global
 
     @Issue("GRADLE-2163")
     @Test void canDecorateBooleanPrimitiveProperties() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file("build.gradle") << """
+        
+        file("build.gradle") << """
             class CustomBean {
                 boolean b
             }
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveIntegrationTest.groovy
index ec5435a..e588286 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveIntegrationTest.groovy
@@ -21,8 +21,9 @@ package org.gradle.api.tasks
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.TestResources
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
+
 import static org.hamcrest.Matchers.equalTo
 
 public class ArchiveIntegrationTest extends AbstractIntegrationSpec {
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveTaskPermissionsIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveTaskPermissionsIntegrationTest.groovy
index 14304bd..dd2a527 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveTaskPermissionsIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveTaskPermissionsIntegrationTest.groovy
@@ -17,10 +17,11 @@
 package org.gradle.api.tasks
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.Requires
-import org.gradle.util.TestFile
 import org.gradle.util.TestPrecondition
 import spock.lang.Unroll
+
 import static org.junit.Assert.assertTrue
 
 class ArchiveTaskPermissionsIntegrationTest extends AbstractIntegrationSpec {
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyErrorIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyErrorIntegrationTest.groovy
index 102fadb..391a6d6 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyErrorIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyErrorIntegrationTest.groovy
@@ -15,16 +15,16 @@
  */
 package org.gradle.api.tasks
 
-import org.gradle.integtests.fixtures.ExecutionFailure
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.util.TestFile
+import org.gradle.integtests.fixtures.executer.ExecutionFailure
+import org.gradle.internal.nativeplatform.filesystem.FileSystems
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.PreconditionVerifier
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
-import org.gradle.util.PreconditionVerifier
 import org.junit.Assert
-import org.junit.Test
 import org.junit.Rule
-import org.gradle.internal.nativeplatform.filesystem.FileSystems
+import org.junit.Test
 
 class CopyErrorIntegrationTest extends AbstractIntegrationTest {
     @Rule public PreconditionVerifier verifier = new PreconditionVerifier()
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyPermissionsIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyPermissionsIntegrationTest.groovy
index c6ce410..b0a2726 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyPermissionsIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyPermissionsIntegrationTest.groovy
@@ -17,9 +17,10 @@
 package org.gradle.api.tasks
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.Requires
-import org.gradle.util.TestFile
 import org.gradle.util.TestPrecondition
+
 import static org.junit.Assert.assertTrue
 
 class CopyPermissionsIntegrationTest extends AbstractIntegrationSpec {
@@ -71,7 +72,7 @@ class CopyPermissionsIntegrationTest extends AbstractIntegrationSpec {
     @Requires(TestPrecondition.FILE_PERMISSIONS)
     def "directory permissions are preserved in copy action"() {
         given:
-        TestFile parent = getTestDir().createDir("testparent")
+        TestFile parent = getTestDirectory().createDir("testparent")
         TestFile child = parent.createDir("testchild")
         child.file("reference.txt") << "test file"
 
@@ -144,7 +145,7 @@ class CopyPermissionsIntegrationTest extends AbstractIntegrationSpec {
     @Requires(TestPrecondition.FILE_PERMISSIONS)
     def "dirMode can be modified in copy task"() {
         given:
-        TestFile parent = getTestDir().createDir("testparent")
+        TestFile parent = getTestDirectory().createDir("testparent")
         TestFile child = parent.createDir("testchild")
         child.file("reference.txt") << "test file"
 
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationTest.groovy
index 194eca7..07bfea3 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationTest.groovy
@@ -15,13 +15,15 @@
  */
 package org.gradle.api.tasks
 
-import org.gradle.integtests.fixtures.TestResources
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.util.TestFile
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
+
+import static org.hamcrest.Matchers.equalTo
+import static org.hamcrest.Matchers.startsWith
+import static org.junit.Assert.assertThat
 
 public class CopyTaskIntegrationTest extends AbstractIntegrationTest {
     @Rule
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ExecutionTimeTaskConfigurationIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ExecutionTimeTaskConfigurationIntegrationTest.groovy
new file mode 100644
index 0000000..110fd58
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ExecutionTimeTaskConfigurationIntegrationTest.groovy
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.internal.SystemProperties
+
+class ExecutionTimeTaskConfigurationIntegrationTest extends AbstractIntegrationSpec {
+
+    private static final String NL = SystemProperties.getLineSeparator()
+
+    def "throws decent warnings when task is configured during execution time"() {
+
+        setup:
+        def invalidConfig = """
+            def anAction = new Action() {
+                public void execute(Object object) {
+                }
+            }
+            doFirst(anAction)
+            doLast(anAction)
+            doFirst {}
+            doLast {}
+
+            actions.set(0, anAction)
+            actions.add(anAction)
+            actions.addAll([anAction])
+
+            def iter = actions.iterator()
+            iter.next()
+            iter.remove()
+
+            actions.removeAll([anAction, anAction])
+            actions.clear()
+
+            onlyIf {false}
+            setActions(new ArrayList())
+            dependsOn bar
+            dependsOn = [bar]
+            setOnlyIf({false})
+            setOnlyIf({false})
+
+            Spec spec = new Spec() {
+                public boolean isSatisfiedBy(Object element) {
+                    return false;
+                }
+            };
+            setOnlyIf(spec)
+            onlyIf(spec)
+            enabled = false
+            deleteAllActions()
+
+            inputs.file("afile")
+            inputs.files("anotherfile")
+            inputs.dir("aDir")
+            inputs.property("propertyName", "propertyValue")
+            inputs.properties(["propertyName": "propertyValue"])
+            inputs.source("aSource")
+            inputs.sourceDir("aSourceDir")
+
+            outputs.upToDateWhen {false}
+            outputs.upToDateWhen(spec)
+            outputs.file("afile")
+            outputs.files("anotherfile")
+            outputs.dir("aDir")
+
+            """
+        when:
+        buildFile.text = """
+            task bar{
+                doLast{
+                    $invalidConfig
+                }
+            }
+
+            task foo << {
+                $invalidConfig
+            }
+        """
+        and:
+        executer.withDeprecationChecksDisabled()
+        then:
+        succeeds("bar", "foo")
+
+        output.contains("Calling Task.doFirst(Action) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling Task.doFirst(Closure) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling Task.doLast(Action) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling Task.doLast(Closure) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling Task.getActions().add() after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling Task.getActions().addAll() after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling Task.getActions().set(int, Object) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling Task.getActions().remove() after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling Task.getActions().removeAll() after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling Task.getActions().clear() after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling Task.onlyIf(Closure) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling Task.setActions(Actions<Task>) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling Task.dependsOn(Object...) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling Task.setDependsOn(Iterable) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling Task.setOnlyIf(Closure) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling Task.setOnlyIf(Spec) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling Task.onlyIf(Spec) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling Task.setEnabled(boolean) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling Task.deleteAllActions() after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling TaskInputs.dir(Object) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling TaskInputs.files(Object...) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling TaskInputs.file(Object) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling TaskInputs.property(String, Object) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling TaskInputs.properties(Map) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling TaskInputs.source(Object) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling TaskInputs.sourceDir(Object) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling TaskOutputs.upToDateWhen(Closure) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling TaskOutputs.upToDateWhen(Spec) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling TaskOutputs.file(Object) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling TaskOutputs.files(Object...) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        output.contains("Calling TaskOutputs.dir(Object) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':bar'.$NL")
+        and:
+
+        output.contains("Calling Task.doFirst(Action) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling Task.doFirst(Closure) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling Task.doLast(Action) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling Task.doLast(Closure) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling Task.getActions().add() after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling Task.getActions().addAll() after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling Task.getActions().set(int, Object) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling Task.getActions().remove() after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling Task.getActions().removeAll() after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling Task.getActions().clear() after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling Task.onlyIf(Closure) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling Task.setActions(Actions<Task>) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling Task.dependsOn(Object...) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling Task.setDependsOn(Iterable) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling Task.setOnlyIf(Closure) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling Task.setOnlyIf(Spec) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling Task.onlyIf(Spec) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling Task.setEnabled(boolean) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling Task.deleteAllActions() after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling TaskInputs.dir(Object) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling TaskInputs.files(Object...) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling TaskInputs.file(Object) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling TaskInputs.property(String, Object) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling TaskInputs.properties(Map) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling TaskInputs.source(Object) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling TaskInputs.sourceDir(Object) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling TaskOutputs.upToDateWhen(Closure) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling TaskOutputs.upToDateWhen(Spec) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling TaskOutputs.file(Object) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling TaskOutputs.files(Object...) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+        output.contains("Calling TaskOutputs.dir(Object) after task execution has started has been deprecated and is scheduled to be removed in Gradle 2.0. Check the configuration of task ':foo'. You may have misused '<<' at task declaration.$NL")
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/FileTreeCopyIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/FileTreeCopyIntegrationTest.groovy
index 4ff2896..d4a094f 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/FileTreeCopyIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/FileTreeCopyIntegrationTest.groovy
@@ -15,11 +15,11 @@
  */
 package org.gradle.api.tasks
 
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.TestResources
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
-import org.gradle.integtests.fixtures.AbstractIntegrationTest
 
 public class FileTreeCopyIntegrationTest extends AbstractIntegrationTest {
     @Rule
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/process/internal/WorkerProcessIntegrationTest.java b/subprojects/core/src/integTest/groovy/org/gradle/process/internal/WorkerProcessIntegrationTest.java
new file mode 100644
index 0000000..ad73aa9
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/process/internal/WorkerProcessIntegrationTest.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.process.internal;
+
+import org.apache.tools.ant.Project;
+import org.gradle.CacheUsage;
+import org.gradle.api.Action;
+import org.gradle.api.internal.Actions;
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.DefaultClassPathProvider;
+import org.gradle.api.internal.DefaultClassPathRegistry;
+import org.gradle.api.internal.classpath.DefaultModuleRegistry;
+import org.gradle.api.internal.classpath.ModuleRegistry;
+import org.gradle.api.internal.file.TestFiles;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.internal.*;
+import org.gradle.internal.id.LongIdGenerator;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
+import org.gradle.internal.nativeplatform.services.NativeServices;
+import org.gradle.listener.ListenerBroadcast;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.messaging.remote.ObjectConnection;
+import org.gradle.messaging.remote.internal.MessagingServices;
+import org.gradle.process.internal.child.WorkerProcessClassPathProvider;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
+import org.jmock.Expectations;
+import org.jmock.Sequence;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class WorkerProcessIntegrationTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final TestListenerInterface listenerMock = context.mock(TestListenerInterface.class);
+    private final MessagingServices messagingServices = new MessagingServices(getClass().getClassLoader());
+    private final MessagingServer server = messagingServices.get(MessagingServer.class);
+    @Rule
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
+    private final ProcessMetaDataProvider metaDataProvider = new DefaultProcessMetaDataProvider(NativeServices.getInstance().get(ProcessEnvironment.class));
+    private final CacheFactory factory = new DefaultCacheFactory(new DefaultFileLockManager(metaDataProvider)).create();
+    private final CacheRepository cacheRepository = new DefaultCacheRepository(tmpDir.getTestDirectory(), null, CacheUsage.ON, factory);
+    private final ModuleRegistry moduleRegistry = new DefaultModuleRegistry();
+    private final ClassPathRegistry classPathRegistry = new DefaultClassPathRegistry(new DefaultClassPathProvider(moduleRegistry), new WorkerProcessClassPathProvider(cacheRepository, moduleRegistry));
+    private final DefaultWorkerProcessFactory workerFactory = new DefaultWorkerProcessFactory(LogLevel.INFO, server, classPathRegistry, TestFiles.resolver(tmpDir.getTestDirectory()), new LongIdGenerator());
+    private final ListenerBroadcast<TestListenerInterface> broadcast = new ListenerBroadcast<TestListenerInterface>(
+            TestListenerInterface.class);
+    private final RemoteExceptionListener exceptionListener = new RemoteExceptionListener(broadcast);
+
+    @Before
+    public void setUp() {
+        broadcast.add(listenerMock);
+    }
+
+    @After
+    public void tearDown() {
+        messagingServices.stop();
+    }
+
+    @Test
+    public void workerProcessCanSendMessagesToThisProcess() throws Throwable {
+        context.checking(new Expectations() {{
+            Sequence sequence = context.sequence("sequence");
+            one(listenerMock).send("message 1", 1);
+            inSequence(sequence);
+            one(listenerMock).send("message 2", 2);
+            inSequence(sequence);
+        }});
+
+        execute(worker(new RemoteProcess()));
+    }
+
+    @Test
+    public void thisProcessCanSendEventsToWorkerProcess() throws Throwable {
+        execute(worker(new PingRemoteProcess()).onServer(new Action<ObjectConnection>() {
+            public void execute(ObjectConnection objectConnection) {
+                TestListenerInterface listener = objectConnection.addOutgoing(TestListenerInterface.class);
+                listener.send("1", 0);
+                listener.send("1", 1);
+                listener.send("1", 2);
+                listener.send("stop", 3);
+            }
+        }));
+    }
+
+    @Test
+    public void multipleWorkerProcessesCanSendMessagesToThisProcess() throws Throwable {
+        context.checking(new Expectations() {{
+            Sequence process1 = context.sequence("sequence1");
+            one(listenerMock).send("message 1", 1);
+            inSequence(process1);
+            one(listenerMock).send("message 2", 2);
+            inSequence(process1);
+            Sequence process2 = context.sequence("sequence2");
+            one(listenerMock).send("other 1", 1);
+            inSequence(process2);
+            one(listenerMock).send("other 2", 2);
+            inSequence(process2);
+        }});
+
+        execute(worker(new RemoteProcess()), worker(new OtherRemoteProcess()));
+    }
+
+    @Test
+    public void handlesWorkerProcessWhichCrashes() throws Throwable {
+        context.checking(new Expectations() {{
+            atMost(1).of(listenerMock).send("message 1", 1);
+            atMost(1).of(listenerMock).send("message 2", 2);
+        }});
+
+        execute(worker(new CrashingRemoteProcess()).expectStopFailure());
+    }
+
+    @Test
+    public void handlesWorkerActionWhichThrowsException() throws Throwable {
+        execute(worker(new BrokenRemoteProcess()).expectStopFailure());
+    }
+
+    @Test
+    public void handlesWorkerActionThatLeavesThreadsRunning() throws Throwable {
+        context.checking(new Expectations() {{
+            one(listenerMock).send("message 1", 1);
+            one(listenerMock).send("message 2", 2);
+        }});
+
+        execute(worker(new NoCleanUpRemoteProcess()));
+    }
+
+    @Test
+    public void handlesWorkerProcessWhichNeverConnects() throws Throwable {
+        execute(worker(new NoConnectRemoteProcess()).expectStartFailure());
+    }
+
+    @Test
+    public void handlesWorkerProcessWhenJvmFailsToStart() throws Throwable {
+        execute(worker(Actions.doNothing()).jvmArgs("--broken").expectStartFailure());
+    }
+
+    private ChildProcess worker(Action<? super WorkerProcessContext> action) {
+        return new ChildProcess(action);
+    }
+
+    void execute(ChildProcess... processes) throws Throwable {
+        for (ChildProcess process : processes) {
+            process.start();
+        }
+        for (ChildProcess process : processes) {
+            process.waitForStop();
+        }
+        messagingServices.stop();
+        exceptionListener.rethrow();
+    }
+
+    private class ChildProcess {
+        private boolean stopFails;
+        private boolean startFails;
+        private WorkerProcess proc;
+        private Action<? super WorkerProcessContext> action;
+        private List<String> jvmArgs = Collections.emptyList();
+        private Action<ObjectConnection> serverAction;
+
+        public ChildProcess(Action<? super WorkerProcessContext> action) {
+            this.action = action;
+        }
+
+        ChildProcess expectStopFailure() {
+            stopFails = true;
+            return this;
+        }
+
+        ChildProcess expectStartFailure() {
+            startFails = true;
+            return this;
+        }
+
+        public void start() {
+            WorkerProcessBuilder builder = workerFactory.create();
+            builder.applicationClasspath(classPathRegistry.getClassPath("ANT").getAsFiles());
+            builder.sharedPackages("org.apache.tools.ant");
+            builder.getJavaCommand().systemProperty("test.system.property", "value");
+            builder.getJavaCommand().environment("TEST_ENV_VAR", "value");
+            builder.worker(action);
+
+            builder.getJavaCommand().jvmArgs(jvmArgs);
+
+            proc = builder.build();
+            try {
+                proc.start();
+                assertFalse(startFails);
+            } catch (ExecException e) {
+                assertTrue(startFails);
+                return;
+            }
+            proc.getConnection().addIncoming(TestListenerInterface.class, exceptionListener);
+            if (serverAction != null) {
+                serverAction.execute(proc.getConnection());
+            }
+        }
+
+        public void waitForStop() {
+            if (startFails) {
+                return;
+            }
+            try {
+                proc.waitForStop();
+                assertFalse("Expected process to fail", stopFails);
+            } catch (ExecException e) {
+                assertTrue("Unexpected failure in worker process", stopFails);
+            }
+        }
+
+        public ChildProcess onServer(Action<ObjectConnection> action) {
+            this.serverAction = action;
+            return this;
+        }
+
+        public ChildProcess jvmArgs(String... jvmArgs) {
+            this.jvmArgs = Arrays.asList(jvmArgs);
+            return this;
+        }
+    }
+
+    public static class RemoteExceptionListener implements Dispatch<MethodInvocation> {
+        Throwable ex;
+        final Dispatch<MethodInvocation> dispatch;
+
+        public RemoteExceptionListener(Dispatch<MethodInvocation> dispatch) {
+            this.dispatch = dispatch;
+        }
+
+        public void dispatch(MethodInvocation message) {
+            try {
+                dispatch.dispatch(message);
+            } catch (Throwable e) {
+                ex = e;
+            }
+        }
+
+        public void rethrow() throws Throwable {
+            if (ex != null) {
+                throw ex;
+            }
+        }
+    }
+
+    public static class RemoteProcess implements Action<WorkerProcessContext>, Serializable {
+        public void execute(WorkerProcessContext workerProcessContext) {
+            // Check environment
+            assertThat(System.getProperty("test.system.property"), equalTo("value"));
+            assertThat(System.getenv().get("TEST_ENV_VAR"), equalTo("value"));
+
+            // Check ClassLoaders
+            ClassLoader antClassLoader = Project.class.getClassLoader();
+            ClassLoader thisClassLoader = getClass().getClassLoader();
+            ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
+
+            assertThat(antClassLoader, not(sameInstance(systemClassLoader)));
+            assertThat(thisClassLoader, not(sameInstance(systemClassLoader)));
+            assertThat(antClassLoader.getParent(), equalTo(systemClassLoader.getParent()));
+            try {
+                assertThat(thisClassLoader.loadClass(Project.class.getName()), sameInstance((Object) Project.class));
+            } catch (ClassNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+
+            // Send some messages
+            TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(
+                    TestListenerInterface.class);
+            sender.send("message 1", 1);
+            sender.send("message 2", 2);
+        }
+    }
+
+    public static class OtherRemoteProcess implements Action<WorkerProcessContext>, Serializable {
+        public void execute(WorkerProcessContext workerProcessContext) {
+            TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(TestListenerInterface.class);
+            sender.send("other 1", 1);
+            sender.send("other 2", 2);
+        }
+    }
+
+    public static class NoCleanUpRemoteProcess implements Action<WorkerProcessContext>, Serializable {
+        public void execute(WorkerProcessContext workerProcessContext) {
+            final Lock lock = new ReentrantLock();
+            lock.lock();
+            new Thread(new Runnable() {
+                public void run() {
+                    lock.lock();
+                }
+            }).start();
+
+            TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(
+                    TestListenerInterface.class);
+            sender.send("message 1", 1);
+            sender.send("message 2", 2);
+        }
+    }
+
+    public static class PingRemoteProcess implements Action<WorkerProcessContext>, Serializable, TestListenerInterface {
+        CountDownLatch stopReceived;
+        int count;
+
+        public void send(String message, int count) {
+            assertEquals(this.count, count);
+            this.count++;
+            if (message.equals("stop")) {
+                assertEquals(4, this.count);
+                stopReceived.countDown();
+            }
+        }
+
+        public void execute(WorkerProcessContext workerProcessContext) {
+            stopReceived = new CountDownLatch(1);
+            workerProcessContext.getServerConnection().addIncoming(TestListenerInterface.class, this);
+            try {
+                stopReceived.await();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    public static class CrashingRemoteProcess implements Action<WorkerProcessContext>, Serializable {
+        public void execute(WorkerProcessContext workerProcessContext) {
+            TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(TestListenerInterface.class);
+            sender.send("message 1", 1);
+            sender.send("message 2", 2);
+            // crash
+            Runtime.getRuntime().halt(1);
+        }
+    }
+
+    public static class BrokenRemoteProcess implements Action<WorkerProcessContext>, Serializable {
+        public void execute(WorkerProcessContext workerProcessContext) {
+            throw new RuntimeException("broken");
+        }
+    }
+
+    public static class NoConnectRemoteProcess implements Action<WorkerProcessContext>, Serializable {
+        private void readObject(ObjectInputStream instr) {
+            System.exit(0);
+        }
+
+        public void execute(WorkerProcessContext workerProcessContext) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    public interface TestListenerInterface {
+        public void send(String message, int count);
+    }
+}
+
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/testfixtures/ProjectBuilderIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/testfixtures/ProjectBuilderIntegrationTest.groovy
new file mode 100644
index 0000000..080f99d
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/testfixtures/ProjectBuilderIntegrationTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.testfixtures
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.junit.Rule
+import org.gradle.test.fixtures.maven.MavenHttpRepository
+
+class ProjectBuilderIntegrationTest extends AbstractIntegrationSpec {
+    @Rule HttpServer server
+
+    def "can resolve remote dependencies"() {
+        def repo = new MavenHttpRepository(server, mavenRepo)
+        repo.module("org.gradle", "a", "1.0").publish().allowAll()
+        server.start()
+
+        when:
+        def project = ProjectBuilder.builder().build()
+        project.with {
+            repositories {
+                maven { url repo.uri }
+            }
+            configurations {
+                compile
+                runtime { extendsFrom compile }
+            }
+            dependencies {
+                compile "org.gradle:a:1.0"
+            }
+        }
+        def compileFiles = project.configurations.compile.files
+        def runtimeFiles = project.configurations.runtime.files
+
+        then:
+        compileFiles.size() == 1
+        runtimeFiles.size() == 1
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/StartParameter.java b/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
index 8419e04..a95bc52 100644
--- a/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
@@ -20,6 +20,11 @@ import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import org.apache.commons.lang.builder.EqualsBuilder;
 import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.gradle.api.Incubating;
+import org.gradle.api.internal.classpath.DefaultModuleRegistry;
+import org.gradle.initialization.CompositeInitScriptFinder;
+import org.gradle.initialization.DistributionInitScriptFinder;
+import org.gradle.initialization.UserHomeInitScriptFinder;
 import org.gradle.internal.SystemProperties;
 import org.gradle.logging.LoggingConfiguration;
 import org.gradle.util.GFileUtils;
@@ -53,6 +58,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
     private Map<String, String> projectProperties = new HashMap<String, String>();
     private Map<String, String> systemPropertiesArgs = new HashMap<String, String>();
     private File gradleUserHomeDir;
+    private File gradleHomeDir;
     private CacheUsage cacheUsage = CacheUsage.ON;
     private File settingsFile;
     private boolean useEmptySettings;
@@ -68,6 +74,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
     private boolean recompileScripts;
     private int parallelThreadCount;
 
+
     /**
      * Sets the project's cache location. Set to null to use the default location.
      */
@@ -97,6 +104,8 @@ public class StartParameter extends LoggingConfiguration implements Serializable
         }
 
         gradleUserHomeDir = GFileUtils.canonicalise(new File(gradleUserHome));
+        gradleHomeDir = new DefaultModuleRegistry().getGradleHome();
+
         setCurrentDir(null);
     }
 
@@ -118,6 +127,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
         startParameter.projectProperties = projectProperties;
         startParameter.systemPropertiesArgs = systemPropertiesArgs;
         startParameter.gradleUserHomeDir = gradleUserHomeDir;
+        startParameter.gradleHomeDir = gradleHomeDir;
         startParameter.cacheUsage = cacheUsage;
         startParameter.initScripts = new ArrayList<File>(initScripts);
         startParameter.setLogLevel(getLogLevel());
@@ -132,6 +142,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
         startParameter.offline = offline;
         startParameter.refreshDependencies = refreshDependencies;
         startParameter.parallelThreadCount = parallelThreadCount;
+
         return startParameter;
     }
 
@@ -458,6 +469,22 @@ public class StartParameter extends LoggingConfiguration implements Serializable
     }
 
     /**
+     * Returns all init scripts, including explicit init scripts and implicit init scripts.
+     *
+     * @return All init scripts, including explicit init scripts and implicit init scripts.
+     */
+    @Incubating
+    public List<File> getAllInitScripts() {
+        CompositeInitScriptFinder initScriptFinder = new CompositeInitScriptFinder(
+                new UserHomeInitScriptFinder(getGradleUserHomeDir()), new DistributionInitScriptFinder(gradleHomeDir)
+        );
+
+        List<File> scripts = new ArrayList<File>(getInitScripts());
+        initScriptFinder.findScripts(scripts);
+        return Collections.unmodifiableList(scripts);
+    }
+
+    /**
      * Sets the project directory to use to select the default project. Use null to use the default criteria for selecting the default project.
      *
      * @param projectDir The project directory. May be null.
@@ -608,6 +635,15 @@ public class StartParameter extends LoggingConfiguration implements Serializable
         this.parallelThreadCount = parallelThreadCount;
     }
 
+    /**
+     * If the configure-on-demand mode is active
+     */
+    @Incubating
+    public boolean isConfigureOnDemand() {
+        //TODO SF this needs to be handled same way as other properties, like 'org.gradle.daemon'
+        return "true".equals(System.getProperty("org.gradle.configuration.ondemand"));
+    }
+
     @Override
     public String toString() {
         return "StartParameter{"
@@ -618,6 +654,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
                 + ", projectProperties=" + projectProperties
                 + ", systemPropertiesArgs=" + systemPropertiesArgs
                 + ", gradleUserHomeDir=" + gradleUserHomeDir
+                + ", gradleHome=" + gradleHomeDir
                 + ", cacheUsage=" + cacheUsage
                 + ", logLevel=" + getLogLevel()
                 + ", showStacktrace=" + getShowStacktrace()
@@ -630,4 +667,11 @@ public class StartParameter extends LoggingConfiguration implements Serializable
                 + ", refreshDependencies=" + refreshDependencies
                 + '}';
     }
+
+    /**
+     * Package scope for testing purposes.
+     */
+    void setGradleHomeDir(File gradleHomeDir) {
+        this.gradleHomeDir = gradleHomeDir;
+    }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/InvalidUserCodeException.java b/subprojects/core/src/main/groovy/org/gradle/api/InvalidUserCodeException.java
new file mode 100644
index 0000000..6d63a5f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/InvalidUserCodeException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api;
+
+import org.gradle.api.internal.Contextual;
+
+/**
+ * A <code>InvalidUserCodeException</code> is thrown when user-provided code cannot be executed.
+ */
+ at Contextual
+public class InvalidUserCodeException extends GradleException {
+    public InvalidUserCodeException() {
+    }
+
+    public InvalidUserCodeException(String message) {
+        super(message);
+    }
+
+    public InvalidUserCodeException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Project.java b/subprojects/core/src/main/groovy/org/gradle/api/Project.java
index 0a16302..f54f9ae 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/Project.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/Project.java
@@ -22,6 +22,7 @@ import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.dsl.ArtifactHandler;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.component.SoftwareComponentContainer;
 import org.gradle.api.file.ConfigurableFileCollection;
 import org.gradle.api.file.ConfigurableFileTree;
 import org.gradle.api.file.CopySpec;
@@ -1471,4 +1472,12 @@ public interface Project extends Comparable<Project>, ExtensionAware {
      * @return Returned instance contains various resource-specific utility methods.
      */
     ResourceHandler getResources();
+
+    /**
+     * Returns the software components produced by this project.
+     *
+     * @return The components for this project.
+     */
+    @Incubating
+    SoftwareComponentContainer getComponents();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencyResolveDetails.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencyResolveDetails.java
new file mode 100644
index 0000000..6984c52
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencyResolveDetails.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.artifacts;
+
+import org.gradle.api.Incubating;
+
+/**
+ * Provides details about a dependency when it is resolved.
+ * Provides means to manipulate dependency metadata when it is resolved.
+ *
+ * @since 1.4
+ */
+ at Incubating
+public interface DependencyResolveDetails {
+
+    /**
+     * The module, before it is resolved.
+     * The requested module does not change even if there are multiple dependency resolve rules
+     * that manipulate the dependency metadata.
+     */
+    ModuleVersionSelector getRequested();
+
+    /**
+     * Allows to override the version when the dependency {@link #getRequested()} is resolved.
+     * Can be used to select a version that is different than requested.
+     * Forcing modules via {@link ResolutionStrategy#force(Object...)} uses this capability.
+     * Configuring a version different than requested will cause {@link #getTarget()} method
+     * return a target module with updated target version.
+     *
+     * It is valid to configure the same version as requested.
+     *
+     * @param version to use when resolving this dependency, cannot be null
+     */
+    void useVersion(String version);
+
+    /**
+     * The target module selector used to resolve the dependency.
+     * Never returns null. Target module is updated when methods like {@link #useVersion(String)} are used.
+     */
+    ModuleVersionSelector getTarget();
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleIdentifier.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleIdentifier.java
new file mode 100644
index 0000000..c466cc6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleIdentifier.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.artifacts;
+
+import java.io.Serializable;
+
+/**
+ * The identifier of a module.
+ */
+public interface ModuleIdentifier extends Serializable {
+
+    /**
+     * The group of the module.
+     *
+     * @return module group
+     */
+    String getGroup();
+
+    /**
+     * The name of the module.
+     *
+     * @return module name
+     */
+    String getName();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleVersionIdentifier.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleVersionIdentifier.java
index b4e6710..907f6d0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleVersionIdentifier.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleVersionIdentifier.java
@@ -15,10 +15,22 @@
  */
 package org.gradle.api.artifacts;
 
+import org.gradle.api.Incubating;
+
+import java.io.Serializable;
+
 /**
  * The identifier of a module version.
  */
-public interface ModuleVersionIdentifier {
+public interface ModuleVersionIdentifier extends Serializable {
+
+    /**
+     * The version of the module
+     *
+     * @return module version
+     */
+    String getVersion();
+
     /**
      * The group of the module.
      *
@@ -34,9 +46,12 @@ public interface ModuleVersionIdentifier {
     String getName();
 
     /**
-     * The version of the module
+     * Returns the {@link ModuleIdentifier} containing the group and the name of this module.
+     * Contains the same information as {@link #getGroup()} and {@link #getVersion()}
      *
-     * @return module version
+     * @return the module identifier
+     * @since 1.4
      */
-    String getVersion();
-}
+    @Incubating
+    ModuleIdentifier getModule();
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolutionStrategy.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolutionStrategy.java
index 98bbebf..b1f0c51 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolutionStrategy.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolutionStrategy.java
@@ -16,6 +16,9 @@
 
 package org.gradle.api.artifacts;
 
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
@@ -25,6 +28,8 @@ import java.util.concurrent.TimeUnit;
  * <p>
  * Examples:
  * <pre autoTested=''>
+ * apply plugin: 'java' //so that there are some configurations
+ *
  * configurations.all {
  *   resolutionStrategy {
  *     // fail eagerly on version conflict (includes transitive dependencies)
@@ -37,8 +42,16 @@ import java.util.concurrent.TimeUnit;
  *     //  *replace existing forced modules with new ones:
  *     forcedModules = ['asm:asm-all:3.3.1']
  *
+ *     // add a dependency resolve rule
+ *     eachDependency { DependencyResolveDetails details ->
+ *       //specifying a fixed version for all libraries with 'org.gradle' group
+ *       if (details.requested.group == 'org.gradle') {
+ *           details.useVersion'1.4'
+ *       }
+ *     }
+ *
  *     // cache dynamic versions for 10 minutes
- *     cacheDynamicVersionsFor 10, 'minutes'
+ *     cacheDynamicVersionsFor 10*60, 'seconds'
  *     // don't cache changing modules at all
  *     cacheChangingModulesFor 0, 'seconds'
  *   }
@@ -56,6 +69,8 @@ public interface ResolutionStrategy {
      * The check includes both first level and transitive dependencies. See example below:
      *
      * <pre autoTested=''>
+     * apply plugin: 'java' //so that there are some configurations
+     *
      * configurations.all {
      *   resolutionStrategy.failOnVersionConflict()
      * }
@@ -78,6 +93,8 @@ public interface ResolutionStrategy {
      * </ul>
      * Example:
      * <pre autoTested=''>
+     * apply plugin: 'java' //so that there are some configurations
+     *
      * configurations.all {
      *   resolutionStrategy.force 'asm:asm-all:3.3.1', 'commons-io:commons-io:1.4'
      * }
@@ -97,6 +114,8 @@ public interface ResolutionStrategy {
      * <p>
      * Example:
      * <pre autoTested=''>
+     * apply plugin: 'java' //so that there are some configurations
+     *
      * configurations.all {
      *   resolutionStrategy.forcedModules = ['asm:asm-all:3.3.1', 'commons-io:commons-io:1.4']
      * }
@@ -117,6 +136,37 @@ public interface ResolutionStrategy {
     Set<ModuleVersionSelector> getForcedModules();
 
     /**
+     * Adds a dependency resolve rule that is triggered for every dependency (including transitive)
+     * when the configuration is being resolved. The action receives an instance of {@link DependencyResolveDetails}
+     * that can be used to find out what dependency is being resolved and to influence the resolution process.
+     * Example:
+     * <pre autoTested=''>
+     * apply plugin: 'java' //so that there are some configurations
+     *
+     * configurations.all {
+     *   resolutionStrategy {
+     *     eachDependency { DependencyResolveDetails details ->
+     *       //specifying a fixed version for all libraries with 'org.gradle' group
+     *       if (details.requested.group == 'org.gradle') {
+     *         details.useVersion '1.4'
+     *       }
+     *     }
+     *     eachDependency {
+     *       //multiple actions can be specified
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * The rules are evaluated in order they are declared. Rules are evaluated after forced modules are applied (see {@link #force(Object...)}
+     *
+     * @return this
+     * @since 1.4
+     */
+    @Incubating
+    ResolutionStrategy eachDependency(Action<? super DependencyResolveDetails> rule);
+
+    /**
      * Sets the length of time that dynamic versions will be cached, with units expressed as a String.
      *
      * <p>A convenience method for {@link #cacheDynamicVersionsFor(int, java.util.concurrent.TimeUnit)} with units expressed as a String.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvableDependencies.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvableDependencies.java
index 097cb95..d3c947d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvableDependencies.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvableDependencies.java
@@ -87,6 +87,7 @@ public interface ResolvableDependencies {
      * that gives access to the graph of the resolved dependencies.
      *
      * @return the resolution result
+     * @since 1.3
      */
     @Incubating
     ResolutionResult getResolutionResult();
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ArtifactResolutionControl.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ArtifactResolutionControl.java
index 636470b..b3fc14d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ArtifactResolutionControl.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ArtifactResolutionControl.java
@@ -25,4 +25,5 @@ import java.io.File;
  */
 @Incubating
 public interface ArtifactResolutionControl extends ResolutionControl<ArtifactIdentifier, File> {
+    boolean belongsToChangingModule();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/DependencyResult.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/DependencyResult.java
index bfb336b..5ce86d8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/DependencyResult.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/DependencyResult.java
@@ -20,23 +20,23 @@ import org.gradle.api.Incubating;
 import org.gradle.api.artifacts.ModuleVersionSelector;
 
 /**
- * Represents the dependency result. An edge in the dependency graph. See also {@link ResolutionResult}.
+ * An edge in the dependency graph. Provides information about the origin of the dependency and the requested module version.
+ *
+ * @see ResolutionResult
  */
 @Incubating
 public interface DependencyResult {
-
     /**
      * Returns the requested module version.
      *
-     * @return requested module version
+     * @return the requested module version
      */
     ModuleVersionSelector getRequested();
 
     /**
-     * Returns the resolved dependent module version result that
-     * lists this dependency result as a dependency.
+     * Returns the origin of the dependency.
      *
-     * @return dependent resolved module version result
+     * @return the origin of the dependency
      */
     ResolvedModuleVersionResult getFrom();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ModuleVersionSelectionReason.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ModuleVersionSelectionReason.java
index 87192aa..e9013d3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ModuleVersionSelectionReason.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ModuleVersionSelectionReason.java
@@ -20,6 +20,8 @@ import org.gradle.api.Incubating;
 
 /**
  * Answers the question why given module version was selected during the dependency resolution
+ *
+ * @since 1.3
  */
 @Incubating
 public interface ModuleVersionSelectionReason {
@@ -38,4 +40,17 @@ public interface ModuleVersionSelectionReason {
      * about conflict resolution and includes means to configure it.
      */
     boolean isConflictResolution();
+
+    /**
+     * Informs whether the module was selected by the dependency resolve rule.
+     * Users can configure dependency resolve rules via {@link org.gradle.api.artifacts.ResolutionStrategy#eachDependency(org.gradle.api.Action)}
+     *
+     * @since 1.4
+     */
+    boolean isSelectedByRule();
+
+    /**
+     * Describes this selection reason.
+     */
+    String getDescription();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolvedDependencyResult.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolvedDependencyResult.java
index ed8d420..20b756e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolvedDependencyResult.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolvedDependencyResult.java
@@ -19,19 +19,13 @@ package org.gradle.api.artifacts.result;
 import org.gradle.api.Incubating;
 
 /**
- * Resolved dependency result is an edge in the resolved dependency graph.
- * Provides information about the requested module version and the selected module version.
- * Requested differs from selected due to number of factors,
- * for example conflict resolution, forcing particular version or when dynamic versions are used.
- * For information about those terms please refer to the user guide.
+ * A dependency that was resolved successfully.
  */
 @Incubating
 public interface ResolvedDependencyResult extends DependencyResult {
-
     /**
-     * Returns the selected module version.
-     *
-     * @return selected module version
+     * Returns the selected module version. This may not necessarily be the same as the requested module version. For example, a dynamic version
+     * may have been requested, or the version may have been substituted due to conflict resolution, or by being forced, or for some other reason.
      */
     ResolvedModuleVersionResult getSelected();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolvedModuleVersionResult.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolvedModuleVersionResult.java
index 4c1fdd5..d0625ae 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolvedModuleVersionResult.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolvedModuleVersionResult.java
@@ -22,42 +22,39 @@ import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import java.util.Set;
 
 /**
- * Resolved module version result is a node in the resolved dependency graph.
- * Contains the identifier of the module and the dependencies.
+ * A node in the resolved dependency graph.
+ * Contains the identifier of the module and its dependencies.
  */
 @Incubating
 public interface ResolvedModuleVersionResult {
 
     /**
-     * The identifier of the resolved module.
+     * Returns the identifier of the resolved module.
      *
-     * @return identifier
+     * @return the identifier of the resolved module
      */
     ModuleVersionIdentifier getId();
 
     /**
-     * The dependencies of the resolved module. See {@link DependencyResult}.
+     * Returns the dependencies of the resolved module.
      * Includes resolved and unresolved dependencies (if any).
      *
-     * @return dependencies
+     * @return the dependencies of the resolved module
      */
     Set<? extends DependencyResult> getDependencies();
 
     /**
-     * The dependents of the resolved module. See {@link ResolvedDependencyResult}.
+     * Returns the dependents of the resolved module.
      *
-     * @return dependents
+     * @return the dependents of the resolved module
      */
     Set<? extends ResolvedDependencyResult> getDependents();
 
     /**
-     * Informs why this module version was selected.
-     * Useful information if during the dependency resolution multiple candidate versions were found
-     * and one of them was selected as a part of conflict resolution.
-     * Informs if a version was forced during the resolution process.
-     * See {@link ModuleVersionSelectionReason}
+     * Returns the reason for selecting the module.
+     * Useful if multiple candidate versions were found during dependency resolution.
      *
-     * @return information why this module version was selected.
+     * @return the reason for selecting the module
      */
     ModuleVersionSelectionReason getSelectionReason();
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/UnresolvedDependencyResult.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/UnresolvedDependencyResult.java
index fafeabd..63e6b87 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/UnresolvedDependencyResult.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/UnresolvedDependencyResult.java
@@ -17,9 +17,25 @@
 package org.gradle.api.artifacts.result;
 
 import org.gradle.api.Incubating;
+import org.gradle.api.artifacts.ModuleVersionSelector;
 
 /**
- * Unresolved dependency result
+ * A dependency that could not be resolved.
  */
 @Incubating
-public interface UnresolvedDependencyResult extends DependencyResult {}
+public interface UnresolvedDependencyResult extends DependencyResult {
+    /**
+     * Returns the module version selector that was attempted to be resolved. This may not be the same as the requested module version.
+     */
+    ModuleVersionSelector getAttempted();
+
+    /**
+     * Returns the reasons why the failed selector was attempted.
+     */
+    ModuleVersionSelectionReason getAttemptedReason();
+
+    /**
+     * The failure that occurred.
+     */
+    Throwable getFailure();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/component/SoftwareComponent.java b/subprojects/core/src/main/groovy/org/gradle/api/component/SoftwareComponent.java
new file mode 100644
index 0000000..a83c2d2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/component/SoftwareComponent.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.component;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+
+/**
+ * A software component produced by a Gradle software project.
+ */
+ at Incubating
+public interface SoftwareComponent extends Named {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/component/SoftwareComponentContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/component/SoftwareComponentContainer.java
new file mode 100644
index 0000000..e38a1df
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/component/SoftwareComponentContainer.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.component;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.NamedDomainObjectSet;
+
+/**
+ * A Container that contains all of the Software Components produced by a Project.
+ */
+ at Incubating
+public interface SoftwareComponentContainer extends NamedDomainObjectSet<SoftwareComponent> {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/component/package-info.java b/subprojects/core/src/main/groovy/org/gradle/api/component/package-info.java
new file mode 100644
index 0000000..d9b22f0
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/component/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Types for declaring and using Software Components.
+ *
+ * @since 1.4
+ */
+ at Incubating
+package org.gradle.api.component;
+
+import org.gradle.api.Incubating;
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/FileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/file/FileTree.java
index 505da29..92ba383 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/FileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/FileTree.java
@@ -56,7 +56,7 @@ public interface FileTree extends FileCollection {
     FileTree matching(PatternFilterable patterns);
 
     /**
-     * Visits the files and directories in this file tree. Files are visited in breadth-wise order, so that a directory
+     * Visits the files and directories in this file tree. Files are visited in depth-first prefix order, so that a directory
      * is visited before its children.
      *
      * @param visitor The visitor.
@@ -65,7 +65,7 @@ public interface FileTree extends FileCollection {
     FileTree visit(FileVisitor visitor);
 
     /**
-     * Visits the files and directories in this file tree. Files are visited in breadth-wise order, so that a directory
+     * Visits the files and directories in this file tree. Files are visited in depth-first prefix order, so that a directory
      * is visited before its children. The file/directory to be visited is passed to the given closure as a {@link
      * FileVisitDetails}
      *
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractTask.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractTask.java
index d591fa1..368316c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractTask.java
@@ -18,14 +18,12 @@ package org.gradle.api.internal;
 
 import groovy.lang.Closure;
 import groovy.lang.MissingPropertyException;
+import groovy.util.ObservableList;
 import org.codehaus.groovy.runtime.InvokerInvocationException;
 import org.gradle.api.*;
 import org.gradle.api.internal.file.TemporaryFileProvider;
 import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.tasks.DefaultTaskDependency;
-import org.gradle.api.internal.tasks.TaskDependencyInternal;
-import org.gradle.api.internal.tasks.TaskExecuter;
-import org.gradle.api.internal.tasks.TaskStateInternal;
+import org.gradle.api.internal.tasks.*;
 import org.gradle.api.internal.tasks.execution.TaskValidator;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
@@ -44,7 +42,10 @@ import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.StandardOutputCapture;
 import org.gradle.util.ConfigureUtil;
+import org.gradle.util.GFileUtils;
 
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
@@ -91,6 +92,9 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
 
     private List<TaskValidator> validators = new ArrayList<TaskValidator>();
 
+    private final TaskStatusNagger taskStatusNagger;
+    private ObservableList observableActionList;
+
     protected AbstractTask() {
         this(taskInfo());
     }
@@ -113,9 +117,17 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
         dependencies = new DefaultTaskDependency(project.getTasks());
         services = project.getServices().createFor(this);
         extensibleDynamicObject = new ExtensibleDynamicObject(this, getServices().get(Instantiator.class));
+        taskStatusNagger = services.get(TaskStatusNagger.class);
         outputs = services.get(TaskOutputsInternal.class);
         inputs = services.get(TaskInputs.class);
         executer = services.get(TaskExecuter.class);
+
+        observableActionList = new ObservableList(actions);
+        observableActionList.addPropertyChangeListener(new PropertyChangeListener() {
+            public void propertyChange(PropertyChangeEvent evt) {
+                taskStatusNagger.nagAboutMutatingListIfTaskNotInConfigurableState("Task.getActions()", evt);
+            }
+        });
         loggingManager = services.get(LoggingManagerInternal.class);
     }
 
@@ -138,6 +150,10 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
         return state;
     }
 
+    public TaskStateInternal getStateInternal() {
+        return state;
+    }
+
     public AntBuilder getAnt() {
         return project.getAnt();
     }
@@ -159,14 +175,19 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
     }
 
     public List<Action<? super Task>> getActions() {
-        return actions;
+        return observableActionList;
     }
 
-    public void setActions(List<Action<? super Task>> actions) {
-        deleteAllActions();
-        for (Action<? super Task> action : actions) {
-            doLast(action);
-        }
+    public void setActions(final List<Action<? super Task>> actions) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.setActions(Actions<Task>)");
+        taskStatusNagger.whileDisabled(new Runnable() {
+            public void run() {
+                deleteAllActions();
+                for (Action<? super Task> action : actions) {
+                    doLast(action);
+                }
+            }
+        });
     }
 
     public TaskDependencyInternal getTaskDependencies() {
@@ -178,22 +199,27 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
     }
 
     public void setDependsOn(Iterable<?> dependsOn) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.setDependsOn(Iterable)");
         dependencies.setValues(dependsOn);
     }
 
     public void onlyIf(Closure onlyIfClosure) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.onlyIf(Closure)");
         this.onlyIfSpec = this.onlyIfSpec.and(onlyIfClosure);
     }
 
     public void onlyIf(Spec<? super Task> onlyIfSpec) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.onlyIf(Spec)");
         this.onlyIfSpec = this.onlyIfSpec.and(onlyIfSpec);
     }
 
     public void setOnlyIf(Spec<? super Task> spec) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.setOnlyIf(Spec)");
         onlyIfSpec = createNewOnlyIfSpec().and(spec);
     }
 
     public void setOnlyIf(Closure onlyIfClosure) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.setOnlyIf(Closure)");
         onlyIfSpec = createNewOnlyIfSpec().and(onlyIfClosure);
     }
 
@@ -226,6 +252,7 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
     }
 
     public void setEnabled(boolean enabled) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.setEnabled(boolean)");
         this.enabled = enabled;
     }
 
@@ -234,6 +261,7 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
     }
 
     public Task deleteAllActions() {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.deleteAllActions()");
         actions.clear();
         return this;
     }
@@ -256,11 +284,13 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
     }
 
     public Task dependsOn(Object... paths) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.dependsOn(Object...)");
         dependencies.add(paths);
         return this;
     }
 
     public Task doFirst(Action<? super Task> action) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.doFirst(Action)");
         if (action == null) {
             throw new InvalidUserDataException("Action must not be null!");
         }
@@ -269,6 +299,7 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
     }
 
     public Task doLast(Action<? super Task> action) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.doLast(Action)");
         if (action == null) {
             throw new InvalidUserDataException("Action must not be null!");
         }
@@ -364,6 +395,7 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
     }
 
     public Task doFirst(Closure action) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.doFirst(Closure)");
         if (action == null) {
             throw new InvalidUserDataException("Action must not be null!");
         }
@@ -372,6 +404,7 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
     }
 
     public Task doLast(Closure action) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.doLast(Closure)");
         if (action == null) {
             throw new InvalidUserDataException("Action must not be null!");
         }
@@ -379,8 +412,13 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
         return this;
     }
 
-    public Task leftShift(Closure action) {
-        return doLast(action);
+    public Task leftShift(final Closure action) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("Task.leftShit(Closure)");
+        if (action == null) {
+            throw new InvalidUserDataException("Action must not be null!");
+        }
+        actions.add(taskStatusNagger.leftShift(convertClosureToAction(action)));
+        return this;
     }
 
     public Task configure(Closure closure) {
@@ -389,7 +427,7 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
 
     public File getTemporaryDir() {
         File dir = getServices().get(TemporaryFileProvider.class).newTemporaryFile(getName());
-        dir.mkdirs();
+        GFileUtils.mkdirs(dir);
         return dir;
     }
 
@@ -401,7 +439,7 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
             }
         };
     }
-    
+
     public void addValidator(TaskValidator validator) {
         validators.add(validator);
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ClosureBackedAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClosureBackedAction.java
index 2c1e920..db7cd91 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ClosureBackedAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClosureBackedAction.java
@@ -65,4 +65,35 @@ public class ClosureBackedAction<T> implements Action<T> {
         }
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        ClosureBackedAction that = (ClosureBackedAction) o;
+
+        if (configureableAware != that.configureableAware) {
+            return false;
+        }
+        if (resolveStrategy != that.resolveStrategy) {
+            return false;
+        }
+        if (!closure.equals(that.closure)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = closure.hashCode();
+        result = 31 * result + (configureableAware ? 1 : 0);
+        result = 31 * result + resolveStrategy;
+        return result;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ConfigureByMapAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConfigureByMapAction.java
new file mode 100644
index 0000000..fa8bacc
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConfigureByMapAction.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal;
+
+import org.gradle.api.Action;
+import org.gradle.util.ConfigureUtil;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+public class ConfigureByMapAction<T> implements Action<T> {
+
+    private final Map<?, ?> properties;
+    private final Collection<?> mandatoryProperties;
+
+    public ConfigureByMapAction(Map<?, ?> properties) {
+        this(properties, Collections.emptySet());
+    }
+
+    public ConfigureByMapAction(Map<?, ?> properties, Collection<?> mandatoryProperties) {
+        this.properties = properties;
+        this.mandatoryProperties = mandatoryProperties;
+    }
+
+    public void execute(T thing) {
+        ConfigureUtil.configureByMap(properties, thing, mandatoryProperties);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        ConfigureByMapAction that = (ConfigureByMapAction) o;
+
+        if (!mandatoryProperties.equals(that.mandatoryProperties)) {
+            return false;
+        }
+        if (!properties.equals(that.properties)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = properties.hashCode();
+        result = 31 * result + mandatoryProperties.hashCode();
+        return result;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/LocationAwareException.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/LocationAwareException.java
index 9ac1544..9044f3c 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/LocationAwareException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/LocationAwareException.java
@@ -148,14 +148,32 @@ public class LocationAwareException extends GradleException {
                 }
                 visitor.endChildren();
             }
-        } else if (t.getCause() != null) {
+            return;
+        }
+
+        if (t.getCause() != null) {
             visitor.startChildren();
-            Throwable cause = t.getCause();
-            visitor.node(cause);
-            if (cause.getClass().getAnnotation(Contextual.class) != null) {
-                visitCauses(cause, visitor);
+            Throwable next = findNearestContextualCause(t);
+            if (next != null) {
+                // Show any contextual cause recursively
+                visitor.node(next);
+                visitCauses(next, visitor);
+            } else {
+                // Show the direct cause of the last contextual cause.
+                visitor.node(t.getCause());
             }
             visitor.endChildren();
         }
     }
+
+    private Throwable findNearestContextualCause(Throwable t) {
+        if (t.getCause() == null) {
+            return null;
+        }
+        Throwable cause = t.getCause();
+        if (cause.getClass().getAnnotation(Contextual.class) != null) {
+            return cause;
+        }
+        return findNearestContextualCause(cause);
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/PropertiesTransformer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/PropertiesTransformer.java
index 194b8cf..e14c4ef 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/PropertiesTransformer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/PropertiesTransformer.java
@@ -15,7 +15,6 @@
  */
 package org.gradle.api.internal;
 
-import groovy.lang.Closure;
 import org.gradle.api.Action;
 import org.gradle.api.Transformer;
 import org.gradle.api.UncheckedIOException;
@@ -40,14 +39,6 @@ public class PropertiesTransformer implements Transformer<Properties, Properties
     }
 
     /**
-     * Adds an action to be executed when properties are transformed.
-     * @param closure the closure to add
-     */
-    public void addAction(Closure closure) {
-        actions.add(closure);
-    }
-    
-    /**
      * Transforms a properties object.  This will modify the
      * original.
      * @param original the properties to transform
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/TaskInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/TaskInternal.java
index e62a9c1..d062bf4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/TaskInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/TaskInternal.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal;
 
 import org.gradle.api.Task;
 import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.api.internal.tasks.TaskStateInternal;
 import org.gradle.api.internal.tasks.execution.TaskValidator;
 import org.gradle.api.specs.Spec;
 import org.gradle.internal.Factory;
@@ -46,6 +47,7 @@ public interface TaskInternal extends Task, Configurable<Task> {
 
     void addValidator(TaskValidator validator);
 
+    TaskStateInternal getStateInternal();
     /**
      * The returned factory is expected to return the same file each time.
      * <p>
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/UserCodeAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/UserCodeAction.java
new file mode 100644
index 0000000..7c8716e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/UserCodeAction.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.InvalidUserCodeException;
+
+/**
+ * A delegating {@link Action} implementation that wraps exceptions in InvalidUserCodeException.
+ * Used to wrap a closure or action parameter that contains user-provided executable code.
+ * @param <T>
+ */
+public class UserCodeAction<T> implements Action<T> {
+    private final String exceptionMessage;
+    private final Action<? super T> delegate;
+
+    public UserCodeAction(String exceptionMessage, Action<? super T> delegate) {
+        this.exceptionMessage = exceptionMessage;
+        this.delegate = delegate;
+    }
+
+    public void execute(T target) {
+        try {
+            delegate.execute(target);
+        } catch (Exception e) {
+            throw new InvalidUserCodeException(exceptionMessage, e);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/XmlTransformer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/XmlTransformer.java
deleted file mode 100644
index 4237340..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/XmlTransformer.java
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal;
-
-import groovy.lang.Closure;
-import groovy.util.IndentPrinter;
-import groovy.util.Node;
-import groovy.util.XmlNodePrinter;
-import groovy.util.XmlParser;
-import org.apache.commons.lang.StringUtils;
-import org.gradle.api.Action;
-import org.gradle.api.Transformer;
-import org.gradle.api.XmlProvider;
-import org.gradle.internal.SystemProperties;
-import org.gradle.internal.UncheckedException;
-import org.gradle.util.GUtil;
-import org.gradle.util.TextUtil;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.xml.sax.InputSource;
-
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-import java.io.*;
-import java.util.ArrayList;
-import java.util.List;
-
-public class XmlTransformer implements Transformer<String, String> {
-    private final List<Action<? super XmlProvider>> actions = new ArrayList<Action<? super XmlProvider>>();
-    private String indentation = "  ";
-
-    public void addAction(Action<? super XmlProvider> provider) {
-        actions.add(provider);
-    }
-
-    public void setIndentation(String indentation) {
-        this.indentation = indentation;
-    }
-
-    public void addAction(Closure closure) {
-        actions.add(new ClosureBackedAction<XmlProvider>(closure));
-    }
-
-    public void transform(File destination, final String encoding, final Action<? super Writer> generator) {
-        IoActions.writeFile(destination, encoding, new Action<Writer>() {
-            public void execute(Writer writer) {
-                transform(writer, encoding, generator);
-            }
-        });
-    }
-
-    public void transform(File destination, final Action<? super Writer> generator) {
-        IoActions.writeFile(destination, new Action<Writer>() {
-            public void execute(Writer writer) {
-                transform(writer, generator);
-            }
-        });
-    }
-
-    public void transform(Writer destination, Action<? super Writer> generator) {
-        StringWriter stringWriter = new StringWriter();
-        generator.execute(stringWriter);
-        transform(stringWriter.toString(), destination);
-    }
-
-    public void transform(Writer destination, String encoding, Action<? super Writer> generator) {
-        StringWriter stringWriter = new StringWriter();
-        generator.execute(stringWriter);
-        transform(stringWriter.toString(), destination, encoding);
-    }
-
-    public String transform(String original) {
-        return doTransform(original).toString();
-    }
-
-    public void transform(String original, Writer destination) {
-        doTransform(original).writeTo(destination);
-    }
-
-    public void transform(String original, Writer destination, String encoding) {
-        doTransform(original).writeTo(destination, encoding);
-    }
-
-    public void transform(String original, OutputStream destination) {
-        doTransform(original).writeTo(destination);
-    }
-
-    public void transform(Node original, Writer destination) {
-        doTransform(original).writeTo(destination);
-    }
-
-    public void transform(Node original, OutputStream destination) {
-        doTransform(original).writeTo(destination);
-    }
-
-    public void transform(DomNode original, Writer destination) {
-        doTransform(original).writeTo(destination);
-    }
-
-    public void transform(DomNode original, OutputStream destination) {
-        doTransform(original).writeTo(destination);
-    }
-
-    private XmlProviderImpl doTransform(String original) {
-        return doTransform(new XmlProviderImpl(original));
-    }
-
-    private XmlProviderImpl doTransform(Node original) {
-        return doTransform(new XmlProviderImpl(original));
-    }
-
-    private XmlProviderImpl doTransform(DomNode original) {
-        return doTransform(new XmlProviderImpl(original));
-    }
-
-    private XmlProviderImpl doTransform(XmlProviderImpl provider) {
-        provider.apply(actions);
-        return provider;
-    }
-
-    private class XmlProviderImpl implements XmlProvider {
-        private StringBuilder builder;
-        private Node node;
-        private String stringValue;
-        private Element element;
-        private String publicId;
-        private String systemId;
-
-        public XmlProviderImpl(String original) {
-            this.stringValue = original;
-        }
-
-        public XmlProviderImpl(Node original) {
-            this.node = original;
-        }
-
-        public XmlProviderImpl(DomNode original) {
-            this.node = original;
-            publicId = original.getPublicId();
-            systemId = original.getSystemId();
-        }
-
-        public void apply(Iterable<Action<? super XmlProvider>> actions) {
-            for (Action<? super XmlProvider> action : actions) {
-                action.execute(this);
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringWriter writer = new StringWriter();
-            writeTo(writer);
-            return writer.toString();
-        }
-
-        public void writeTo(Writer writer) {
-            doWriteTo(writer, null);
-        }
-
-        public void writeTo(Writer writer, String encoding) {
-            doWriteTo(writer, encoding);
-        }
-
-        public void writeTo(OutputStream stream) {
-            try {
-                Writer writer = new OutputStreamWriter(stream, "UTF-8");
-                doWriteTo(writer, "UTF-8");
-                writer.flush();
-            } catch (IOException e) {
-                throw UncheckedException.throwAsUncheckedException(e);
-            }
-        }
-
-        public StringBuilder asString() {
-            if (builder == null) {
-                builder = new StringBuilder(toString());
-                node = null;
-                element = null;
-            }
-            return builder;
-        }
-
-        public Node asNode() {
-            if (node == null) {
-                try {
-                    node = new XmlParser().parseText(toString());
-                } catch (Exception e) {
-                    throw UncheckedException.throwAsUncheckedException(e);
-                }
-                builder = null;
-                element = null;
-            }
-            return node;
-        }
-
-        public Element asElement() {
-            if (element == null) {
-                Document document;
-                try {
-                    document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new StringReader(toString())));
-                } catch (Exception e) {
-                    throw UncheckedException.throwAsUncheckedException(e);
-                }
-                element = document.getDocumentElement();
-                builder = null;
-                node = null;
-            }
-            return element;
-        }
-
-        private void doWriteTo(Writer writer, String encoding) {
-            writeXmlDeclaration(writer, encoding);
-
-            try {
-                if (node != null) {
-                    printNode(node, writer);
-                } else if (element != null) {
-                    printDomNode(element, writer);
-                } else if (builder != null) {
-                    writer.append(TextUtil.toPlatformLineSeparators(stripXmlDeclaration(builder)));
-                } else {
-                    writer.append(TextUtil.toPlatformLineSeparators(stripXmlDeclaration(stringValue)));
-                }
-            } catch (IOException e) {
-                throw UncheckedException.throwAsUncheckedException(e);
-            }
-        }
-
-        private void printNode(Node node, Writer writer) {
-            final PrintWriter printWriter = new PrintWriter(writer);
-            if (GUtil.isTrue(publicId)) {
-                printWriter.format("<!DOCTYPE %s PUBLIC \"%s\" \"%s\">%n", node.name(), publicId, systemId);
-            }
-            IndentPrinter indentPrinter = new IndentPrinter(printWriter, indentation) {
-                @Override
-                public void println() {
-                    printWriter.println();
-                }
-            };
-            XmlNodePrinter nodePrinter = new XmlNodePrinter(indentPrinter);
-            nodePrinter.setPreserveWhitespace(true);
-            nodePrinter.print(node);
-            printWriter.flush();
-        }
-
-        private void printDomNode(org.w3c.dom.Node node, Writer destination) {
-            removeEmptyTextNodes(node); // empty text nodes hinder subsequent formatting
-            int indentAmount = determineIndentAmount();
-
-            try {
-                TransformerFactory factory = TransformerFactory.newInstance();
-                try {
-                    factory.setAttribute("indent-number", indentAmount);
-                } catch (IllegalArgumentException ignored) {
-                    /* unsupported by this transformer */
-                }
-
-                javax.xml.transform.Transformer transformer = factory.newTransformer();
-                transformer.setOutputProperty(OutputKeys.METHOD, "xml");
-                transformer.setOutputProperty(OutputKeys.INDENT, "yes");
-                transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
-                if (GUtil.isTrue(publicId)) {
-                    transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, publicId);
-                    transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, systemId);
-                }
-                try {
-                    // some impls support this but not factory.setAttribute("indent-number")
-                    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", String.valueOf(indentAmount));
-                } catch (IllegalArgumentException ignored) {
-                    /* unsupported by this transformer */
-                }
-
-                transformer.transform(new DOMSource(node), new StreamResult(destination));
-            } catch (TransformerException e) {
-                throw UncheckedException.throwAsUncheckedException(e);
-            }
-        }
-
-        private int determineIndentAmount() {
-            if (indentation.equals("\t")) { // not supported by javax.xml.transform.Transformer; use two spaces instead
-                return 2;
-            }
-            return indentation.length(); // assume indentation uses spaces
-        }
-
-        private void removeEmptyTextNodes(org.w3c.dom.Node node) {
-            org.w3c.dom.NodeList children = node.getChildNodes();
-
-            for (int i = 0; i < children.getLength(); i++) {
-                org.w3c.dom.Node child = children.item(i);
-                if (child.getNodeType() == org.w3c.dom.Node.TEXT_NODE && child.getNodeValue().trim().length() == 0) {
-                    node.removeChild(child);
-                } else {
-                    removeEmptyTextNodes(child);
-                }
-            }
-        }
-
-        private void writeXmlDeclaration(Writer writer, String encoding) {
-            try {
-                writer.write("<?xml version=\"1.0\"");
-                if (encoding != null) {
-                    writer.write(" encoding=\"");
-                    writer.write(encoding);
-                    writer.write("\"");
-                }
-                writer.write("?>");
-                writer.write(SystemProperties.getLineSeparator());
-            } catch (IOException e) {
-                throw UncheckedException.throwAsUncheckedException(e);
-            }
-        }
-        private boolean hasXmlDeclaration(String xml) {
-            return xml.startsWith("<?xml"); // XML declarations must be located at first position of first line
-        }
-
-        private String stripXmlDeclaration(CharSequence sequence) {
-            String str = sequence.toString();
-            if (hasXmlDeclaration(str)) {
-                str = str.substring(str.indexOf("?>") + 2);
-                str = StringUtils.stripStart(str, null);
-            }
-            return str;
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublicationServices.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublicationServices.java
index dcfbbc8..5291a1f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublicationServices.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublicationServices.java
@@ -16,9 +16,16 @@
 package org.gradle.api.internal.artifacts;
 
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.internal.artifacts.ivyservice.IvyModuleDescriptorWriter;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
 
 public interface ArtifactPublicationServices {
-    ArtifactPublisher getArtifactPublisher();
 
-    RepositoryHandler getRepositoryHandler();
+    RepositoryHandler createRepositoryHandler();
+
+    ModuleDescriptorConverter getDescriptorFileModuleConverter();
+
+    IvyModuleDescriptorWriter getIvyModuleDescriptorWriter();
+
+    ArtifactPublisher createArtifactPublisher();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisher.java
index dc617f2..a0a4ce9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisher.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisher.java
@@ -15,11 +15,10 @@
  */
 package org.gradle.api.internal.artifacts;
 
-import org.gradle.api.Nullable;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.Module;
 import org.gradle.api.artifacts.PublishException;
-import org.gradle.api.internal.XmlTransformer;
 
 import java.io.File;
 import java.util.Set;
@@ -28,5 +27,5 @@ import java.util.Set;
  * @author Hans Dockter
  */
 public interface ArtifactPublisher {
-    void publish(Module module, Set<? extends Configuration> configurations, File descriptorDestination, @Nullable XmlTransformer descriptorTransformer) throws PublishException;
+    void publish(Iterable<DependencyResolver> dependencyResolvers, Module module, Set<? extends Configuration> configurations, File descriptor) throws PublishException;
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisherFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisherFactory.java
deleted file mode 100644
index 8b426b7..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisherFactory.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts;
-
-import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
-
-public interface ArtifactPublisherFactory {
-
-    ArtifactPublisher createArtifactPublisher(ArtifactRepositoryInternal repository);
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactPublisherFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactPublisherFactory.java
deleted file mode 100644
index 9ee7459..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactPublisherFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts;
-
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.Transformer;
-import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
-import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * This is a temporary measure while we are having to deal with parallel publication mechanisms.
- */
-public class DefaultArtifactPublisherFactory implements ArtifactPublisherFactory {
-
-    private final Transformer<ArtifactPublisher, ResolverProvider> providerToPublisherTransformer;
-
-    public DefaultArtifactPublisherFactory(Transformer<ArtifactPublisher, ResolverProvider> providerToPublisherTransformer) {
-        this.providerToPublisherTransformer = providerToPublisherTransformer;
-    }
-
-    public ArtifactPublisher createArtifactPublisher(final ArtifactRepositoryInternal repository) {
-        return providerToPublisherTransformer.transform(new ResolverProvider() {
-            public List<DependencyResolver> getResolvers() {
-                return Collections.singletonList(repository.createResolver());
-            }
-        });
-    }
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainer.java
index c5e9fb1..3d88572 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainer.java
@@ -25,6 +25,7 @@ import org.gradle.api.UnknownDomainObjectException;
 import org.gradle.api.artifacts.ArtifactRepositoryContainer;
 import org.gradle.api.artifacts.UnknownRepositoryException;
 import org.gradle.api.artifacts.repositories.ArtifactRepository;
+import org.gradle.api.internal.Actions;
 import org.gradle.api.internal.DefaultNamedDomainObjectList;
 import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
 import org.gradle.internal.reflect.Instantiator;
@@ -34,7 +35,6 @@ import org.gradle.util.GUtil;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 
 import static org.gradle.api.internal.Cast.cast;
 
@@ -157,7 +157,7 @@ public class DefaultArtifactRepositoryContainer extends DefaultNamedDomainObject
         DependencyResolver resolver = baseRepositoryFactory.toResolver(repository);
         ConfigureUtil.configure(configureClosure, resolver);
         ArtifactRepository resolverRepository = baseRepositoryFactory.createResolverBackedRepository(resolver);
-        addRepository(resolverRepository, "repository", orderAction);
+        addWithUniqueName(resolverRepository, "repository", orderAction);
         return resolver;
     }
 
@@ -174,48 +174,23 @@ public class DefaultArtifactRepositoryContainer extends DefaultNamedDomainObject
         return returnedResolvers;
     }
 
-    public <T extends ArtifactRepository> T addRepository(T repository, Closure closure, String defaultName) {
-        return addRepository(repository, closure, defaultName, addLastAction);
-    }
-
-    public <T extends ArtifactRepository> T addRepository(T repository, Map<String, ?> args, Closure closure, String defaultName) {
-        ConfigureUtil.configureByMap(args, repository);
-        return addRepository(repository, closure, defaultName);
-    }
-
-    protected <T extends ArtifactRepository> T addRepository(T repository, Closure closure, String defaultName, Action<ArtifactRepository> action) {
-        ConfigureUtil.configure(closure, repository);
-        return addRepository(repository, defaultName, action);
-    }
-
-    public <T extends ArtifactRepository> T addRepository(T repository, Map<String, ?> args, String defaultName) {
-        ConfigureUtil.configureByMap(args, repository);
-        addRepository(repository, defaultName);
-        return repository;
+    public <T extends ArtifactRepository> T addRepository(T repository, String defaultName) {
+        return addRepository(repository, defaultName, Actions.doNothing());
     }
 
-    public <T extends ArtifactRepository> T addRepository(T repository, String defaultName) {
-        return addRepository(repository, defaultName, addLastAction);
+    public <T extends ArtifactRepository> T addRepository(T repository, String defaultName, Action<? super T> configureAction) {
+        configureAction.execute(repository);
+        return addWithUniqueName(repository, defaultName, addLastAction);
     }
 
-    protected <T extends ArtifactRepository> T addRepository(T repository, String defaultName, Action<ArtifactRepository> action) {
+    private <T extends ArtifactRepository> T addWithUniqueName(T repository, String defaultName, Action<? super T> insertion) {
         String repositoryName = repository.getName();
         if (!GUtil.isTrue(repositoryName)) {
-            repositoryName = uniquifyName(defaultName);
-            repository.setName(repositoryName);
+            repository.setName(uniquifyName(defaultName));
+        } else {
+            repository.setName(uniquifyName(repositoryName));
         }
-        assertCanAdd(repositoryName);
-        action.execute(repository);
-        cast(ArtifactRepositoryInternal.class, repository).onAddToContainer(this);
-        return repository;
-    }
-
-    protected <T extends ArtifactRepository> T addRepository(T repository) {
-        return addRepository(repository, addLastAction);
-    }
 
-    protected <T extends ArtifactRepository> T addRepository(T repository, Action<? super T> insertion) {
-        repository.setName(uniquifyName(repository.getName()));
         assertCanAdd(repository.getName());
         insertion.execute(repository);
         cast(ArtifactRepositoryInternal.class, repository).onAddToContainer(this);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifier.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifier.java
deleted file mode 100755
index 9c83315..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifier.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts;
-
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-
-public class DefaultModuleVersionIdentifier implements ModuleVersionIdentifier {
-    private final String group;
-    private final String name;
-    private final String version;
-
-    public DefaultModuleVersionIdentifier(String group, String name, String version) {
-        assert group != null : "group cannot be null";
-        assert name != null : "name cannot be null";
-        assert version != null : "version cannot be null";
-        this.group = group;
-        this.name = name;
-        this.version = version;
-    }
-
-    public String getGroup() {
-        return group;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public String getVersion() {
-        return version;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("{group: %s, module: %s, version: %s}", group, name, version);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj == null || obj.getClass() != getClass()) {
-            return false;
-        }
-        DefaultModuleVersionIdentifier other = (DefaultModuleVersionIdentifier) obj;
-        if (!group.equals(other.group)) {
-            return false;
-        }
-        if (!name.equals(other.name)) {
-            return false;
-        }
-        if (!version.equals(other.version)) {
-            return false;
-        }
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        return group.hashCode() ^ name.hashCode() ^ version.hashCode();
-    }
-
-    public static ModuleVersionIdentifier newId(Module module) {
-        return new DefaultModuleVersionIdentifier(module.getGroup(), module.getName(), module.getVersion());
-    }
-
-    public static ModuleVersionIdentifier newId(String group, String name, String version) {
-        return new DefaultModuleVersionIdentifier(group, name, version);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelector.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelector.java
deleted file mode 100644
index b187453..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelector.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts;
-
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.artifacts.ModuleVersionSelector;
-
-/**
- * by Szczepan Faber, created at: 11/13/11
- */
-public class DefaultModuleVersionSelector implements ModuleVersionSelector {
-
-    private String group;
-    private String name;
-    private String version;
-
-    public DefaultModuleVersionSelector(String group, String name, String version) {
-        this.group = group;
-        this.name = name;
-        this.version = version;
-    }
-
-    public String getGroup() {
-        return group;
-    }
-
-    public DefaultModuleVersionSelector setGroup(String group) {
-        this.group = group;
-        return this;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public DefaultModuleVersionSelector setName(String name) {
-        this.name = name;
-        return this;
-    }
-
-    public String getVersion() {
-        return version;
-    }
-
-    public boolean matchesStrictly(ModuleVersionIdentifier identifier) {
-        return new ModuleVersionSelectorStrictSpec(this).isSatisfiedBy(identifier);
-    }
-
-    public DefaultModuleVersionSelector setVersion(String version) {
-        this.version = version;
-        return this;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("ModuleVersionSelector{group: %s, module: %s, version: %s}", group, name, version);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof DefaultModuleVersionSelector)) {
-            return false;
-        }
-
-        DefaultModuleVersionSelector that = (DefaultModuleVersionSelector) o;
-
-        if (group != null ? !group.equals(that.group) : that.group != null) {
-            return false;
-        }
-        if (name != null ? !name.equals(that.name) : that.name != null) {
-            return false;
-        }
-        if (version != null ? !version.equals(that.version) : that.version != null) {
-            return false;
-        }
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = group != null ? group.hashCode() : 0;
-        result = 31 * result + (name != null ? name.hashCode() : 0);
-        result = 31 * result + (version != null ? version.hashCode() : 0);
-        return result;
-    }
-
-    public static ModuleVersionSelector newSelector(String group, String name, String version) {
-        return new DefaultModuleVersionSelector(group, name, version);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolutionServices.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolutionServices.java
index e1c5b6b..8aa927a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolutionServices.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolutionServices.java
@@ -18,7 +18,6 @@ package org.gradle.api.internal.artifacts;
 import org.gradle.api.artifacts.dsl.ArtifactHandler;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.internal.Factory;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
 
 public interface DependencyResolutionServices {
@@ -30,7 +29,7 @@ public interface DependencyResolutionServices {
 
     ArtifactHandler getArtifactHandler();
 
-    Factory<ArtifactPublicationServices> getPublishServicesFactory();
+    ArtifactPublicationServices createArtifactPublicationServices();
 
     BaseRepositoryFactory getBaseRepositoryFactory();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolveDetailsInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolveDetailsInternal.java
new file mode 100644
index 0000000..15738f2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolveDetailsInternal.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts;
+
+import org.gradle.api.artifacts.DependencyResolveDetails;
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+
+/**
+ * by Szczepan Faber, created at: 12/13/12
+ */
+public interface DependencyResolveDetailsInternal extends DependencyResolveDetails {
+
+    void useVersion(String version, ModuleVersionSelectionReason selectionReason);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ProjectBackedModule.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ProjectBackedModule.java
new file mode 100644
index 0000000..6242f57
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ProjectBackedModule.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Module;
+
+public class ProjectBackedModule implements Module {
+
+    private final Project project;
+
+    public ProjectBackedModule(Project project) {
+        this.project = project;
+    }
+
+    public String getGroup() {
+        return project.getGroup().toString();
+    }
+
+    public String getName() {
+        return project.getName();
+    }
+
+    public String getVersion() {
+        return project.getVersion().toString();
+    }
+
+    public String getStatus() {
+        return project.getStatus().toString();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java
deleted file mode 100644
index c2eb6be..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java
+++ /dev/null
@@ -1,568 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.configurations;
-
-import groovy.lang.Closure;
-import org.gradle.api.*;
-import org.gradle.api.artifacts.*;
-import org.gradle.api.artifacts.result.ResolutionResult;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.CompositeDomainObjectSet;
-import org.gradle.api.internal.DefaultDomainObjectSet;
-import org.gradle.api.internal.artifacts.*;
-import org.gradle.api.internal.file.AbstractFileCollection;
-import org.gradle.api.internal.tasks.AbstractTaskDependency;
-import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.specs.Specs;
-import org.gradle.api.tasks.TaskDependency;
-import org.gradle.listener.ListenerBroadcast;
-import org.gradle.listener.ListenerManager;
-import org.gradle.util.CollectionUtils;
-import org.gradle.util.ConfigureUtil;
-import org.gradle.util.WrapUtil;
-
-import java.io.File;
-import java.util.*;
-
-import static org.apache.ivy.core.module.descriptor.Configuration.Visibility;
-
-public class DefaultConfiguration extends AbstractFileCollection implements ConfigurationInternal {
-    private final String path;
-    private final String name;
-
-    private Visibility visibility = Visibility.PUBLIC;
-    private boolean transitive = true;
-    private Set<Configuration> extendsFrom = new LinkedHashSet<Configuration>();
-    private String description;
-    private ConfigurationsProvider configurationsProvider;
-    private final ArtifactDependencyResolver dependencyResolver;
-    private final ListenerManager listenerManager;
-    private final DependencyMetaDataProvider metaDataProvider;
-    private final DefaultDependencySet dependencies;
-    private final CompositeDomainObjectSet<Dependency> inheritedDependencies;
-    private final DefaultDependencySet allDependencies;
-    private final DefaultPublishArtifactSet artifacts;
-    private final CompositeDomainObjectSet<PublishArtifact> inheritedArtifacts;
-    private final DefaultPublishArtifactSet allArtifacts;
-    private final ConfigurationResolvableDependencies resolvableDependencies = new ConfigurationResolvableDependencies();
-    private final ListenerBroadcast<DependencyResolutionListener> resolutionListenerBroadcast;
-    private Set<ExcludeRule> excludeRules = new LinkedHashSet<ExcludeRule>();
-
-    // This lock only protects the following fields
-    private final Object lock = new Object();
-    private State state = State.UNRESOLVED;
-    private ResolverResults cachedResolverResults;
-    private final DefaultResolutionStrategy resolutionStrategy;
-
-    public DefaultConfiguration(String path, String name, ConfigurationsProvider configurationsProvider,
-                                ArtifactDependencyResolver dependencyResolver, ListenerManager listenerManager,
-                                DependencyMetaDataProvider metaDataProvider, DefaultResolutionStrategy resolutionStrategy) {
-        this.path = path;
-        this.name = name;
-        this.configurationsProvider = configurationsProvider;
-        this.dependencyResolver = dependencyResolver;
-        this.listenerManager = listenerManager;
-        this.metaDataProvider = metaDataProvider;
-        assert resolutionStrategy != null : "Cannot create configuration with null resolutionStrategy";
-        this.resolutionStrategy = resolutionStrategy;
-
-        resolutionListenerBroadcast = listenerManager.createAnonymousBroadcaster(DependencyResolutionListener.class);
-
-        DefaultDomainObjectSet<Dependency> ownDependencies = new DefaultDomainObjectSet<Dependency>(Dependency.class);
-        ownDependencies.beforeChange(new VetoContainerChangeAction());
-
-        dependencies = new DefaultDependencySet(String.format("%s dependencies", getDisplayName()), ownDependencies);
-        inheritedDependencies = new CompositeDomainObjectSet<Dependency>(Dependency.class, ownDependencies);
-        allDependencies = new DefaultDependencySet(String.format("%s all dependencies", getDisplayName()), inheritedDependencies);
-
-        DefaultDomainObjectSet<PublishArtifact> ownArtifacts = new DefaultDomainObjectSet<PublishArtifact>(PublishArtifact.class);
-        ownArtifacts.beforeChange(new VetoContainerChangeAction());
-        artifacts = new DefaultPublishArtifactSet(String.format("%s artifacts", getDisplayName()), ownArtifacts);
-        inheritedArtifacts = new CompositeDomainObjectSet<PublishArtifact>(PublishArtifact.class, ownArtifacts);
-        allArtifacts = new DefaultPublishArtifactSet(String.format("%s all artifacts", getDisplayName()), inheritedArtifacts);
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public State getState() {
-        synchronized (lock) {
-            return state;
-        }
-    }
-
-    public Module getModule() {
-        return metaDataProvider.getModule();
-    }
-
-    public boolean isVisible() {
-        return visibility == Visibility.PUBLIC;
-    }
-
-    public Configuration setVisible(boolean visible) {
-        throwExceptionIfNotInUnresolvedState();
-        this.visibility = visible ? Visibility.PUBLIC : Visibility.PRIVATE;
-        return this;
-    }
-
-    public Set<Configuration> getExtendsFrom() {
-        return Collections.unmodifiableSet(extendsFrom);
-    }
-
-    public Configuration setExtendsFrom(Set<Configuration> extendsFrom) {
-        throwExceptionIfNotInUnresolvedState();
-        for (Configuration configuration : this.extendsFrom) {
-            inheritedArtifacts.removeCollection(configuration.getAllArtifacts());
-            inheritedDependencies.removeCollection(configuration.getAllDependencies());
-        }
-        this.extendsFrom = new HashSet<Configuration>();
-        for (Configuration configuration : extendsFrom) {
-            extendsFrom(configuration);
-        }
-        return this;
-    }
-
-    public Configuration extendsFrom(Configuration... extendsFrom) {
-        throwExceptionIfNotInUnresolvedState();
-        for (Configuration configuration : extendsFrom) {
-            if (configuration.getHierarchy().contains(this)) {
-                throw new InvalidUserDataException(String.format(
-                        "Cyclic extendsFrom from %s and %s is not allowed. See existing hierarchy: %s", this,
-                        configuration, configuration.getHierarchy()));
-            }
-            this.extendsFrom.add(configuration);
-            inheritedArtifacts.addCollection(configuration.getAllArtifacts());
-            inheritedDependencies.addCollection(configuration.getAllDependencies());
-        }
-        return this;
-    }
-
-    public boolean isTransitive() {
-        return transitive;
-    }
-
-    public Configuration setTransitive(boolean transitive) {
-        throwExceptionIfNotInUnresolvedState();
-        this.transitive = transitive;
-        return this;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public Configuration setDescription(String description) {
-        throwExceptionIfNotInUnresolvedState();
-        this.description = description;
-        return this;
-    }
-
-    public Set<Configuration> getHierarchy() {
-        Set<Configuration> result = WrapUtil.<Configuration>toLinkedSet(this);
-        collectSuperConfigs(this, result);
-        return result;
-    }
-
-    private void collectSuperConfigs(Configuration configuration, Set<Configuration> result) {
-        for (Configuration superConfig : configuration.getExtendsFrom()) {
-            if (result.contains(superConfig)) {
-                result.remove(superConfig);
-            }
-            result.add(superConfig);
-            collectSuperConfigs(superConfig, result);
-        }
-    }
-
-    public Set<Configuration> getAll() {
-        return configurationsProvider.getAll();
-    }
-
-    public Set<File> resolve() {
-        return getFiles();
-    }
-
-    public Set<File> getFiles() {
-        return fileCollection(Specs.SATISFIES_ALL).getFiles();
-    }
-
-    public Set<File> files(Dependency... dependencies) {
-        return fileCollection(dependencies).getFiles();
-    }
-
-    public Set<File> files(Closure dependencySpecClosure) {
-        return fileCollection(dependencySpecClosure).getFiles();
-    }
-
-    public Set<File> files(Spec<? super Dependency> dependencySpec) {
-        return fileCollection(dependencySpec).getFiles();
-    }
-
-    public FileCollection fileCollection(Spec<? super Dependency> dependencySpec) {
-        return new ConfigurationFileCollection(dependencySpec);
-    }
-
-    public FileCollection fileCollection(Closure dependencySpecClosure) {
-        return new ConfigurationFileCollection(dependencySpecClosure);
-    }
-
-    public FileCollection fileCollection(Dependency... dependencies) {
-        return new ConfigurationFileCollection(WrapUtil.toLinkedSet(dependencies));
-    }
-
-    public ResolvedConfiguration getResolvedConfiguration() {
-        resolveNow();
-        return cachedResolverResults.getResolvedConfiguration();
-    }
-
-    private void resolveNow() {
-        synchronized (lock) {
-            if (state == State.UNRESOLVED) {
-                DependencyResolutionListener broadcast = getDependencyResolutionBroadcast();
-                ResolvableDependencies incoming = getIncoming();
-                broadcast.beforeResolve(incoming);
-                cachedResolverResults = dependencyResolver.resolve(this);
-                if (cachedResolverResults.getResolvedConfiguration().hasError()) {
-                    state = State.RESOLVED_WITH_FAILURES;
-                } else {
-                    state = State.RESOLVED;
-                }
-                broadcast.afterResolve(incoming);
-            }
-        }
-    }
-
-    public TaskDependency getBuildDependencies() {
-        return allDependencies.getBuildDependencies();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public TaskDependency getTaskDependencyFromProjectDependency(final boolean useDependedOn, final String taskName) {
-        return new AbstractTaskDependency() {
-            public void resolve(TaskDependencyResolveContext context) {
-                if (useDependedOn) {
-                    addTaskDependenciesFromProjectsIDependOn(taskName, context);
-                } else {
-                    Project thisProject = context.getTask().getProject();
-                    addTaskDependenciesFromProjectsDependingOnMe(thisProject, taskName, context);
-                }
-            }
-
-            private void addTaskDependenciesFromProjectsIDependOn(final String taskName,
-                                                                  final TaskDependencyResolveContext context) {
-                Set<ProjectDependency> projectDependencies = getAllDependencies().withType(ProjectDependency.class);
-                for (ProjectDependency projectDependency : projectDependencies) {
-                    Task nextTask = projectDependency.getDependencyProject().getTasks().findByName(taskName);
-                    if (nextTask != null) {
-                        context.add(nextTask);
-                    }
-                }
-            }
-
-            private void addTaskDependenciesFromProjectsDependingOnMe(final Project thisProject, final String taskName,
-                                                                      final TaskDependencyResolveContext context) {
-                Set<Task> tasksWithName = thisProject.getRootProject().getTasksByName(taskName, true);
-                for (Task nextTask : tasksWithName) {
-                    Configuration configuration = nextTask.getProject().getConfigurations().findByName(getName());
-                    if (configuration != null && doesConfigurationDependOnProject(configuration, thisProject)) {
-                        context.add(nextTask);
-                    }
-                }
-            }
-        };
-    }
-
-    private static boolean doesConfigurationDependOnProject(Configuration configuration, Project project) {
-        Set<ProjectDependency> projectDependencies = configuration.getAllDependencies().withType(ProjectDependency.class);
-        for (ProjectDependency projectDependency : projectDependencies) {
-            if (projectDependency.getDependencyProject().equals(project)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public DependencySet getDependencies() {
-        return dependencies;
-    }
-
-    public DependencySet getAllDependencies() {
-        return allDependencies;
-    }
-
-    public PublishArtifactSet getArtifacts() {
-        return artifacts;
-    }
-
-    public PublishArtifactSet getAllArtifacts() {
-        return allArtifacts;
-    }
-
-    public Set<ExcludeRule> getExcludeRules() {
-        return Collections.unmodifiableSet(excludeRules);
-    }
-
-    public void setExcludeRules(Set<ExcludeRule> excludeRules) {
-        throwExceptionIfNotInUnresolvedState();
-        this.excludeRules = excludeRules;
-    }
-
-    public DefaultConfiguration exclude(Map<String, String> excludeRuleArgs) {
-        throwExceptionIfNotInUnresolvedState();
-        excludeRules.add(new DefaultExcludeRule(excludeRuleArgs.get(ExcludeRule.GROUP_KEY), excludeRuleArgs.get(ExcludeRule.MODULE_KEY)));
-        return this;
-    }
-
-    public String getUploadTaskName() {
-        return Configurations.uploadTaskName(getName());
-    }
-
-    public String getDisplayName() {
-        return String.format("configuration '%s'", path);
-    }
-
-    public ResolvableDependencies getIncoming() {
-        return resolvableDependencies;
-    }
-
-    public Configuration copy() {
-        return createCopy(getDependencies(), false);
-    }
-
-    public Configuration copyRecursive() {
-        return createCopy(getAllDependencies(), true);
-    }
-
-    public Configuration copy(Spec<? super Dependency> dependencySpec) {
-        return createCopy(CollectionUtils.filter(getDependencies(), dependencySpec), false);
-    }
-
-    public Configuration copyRecursive(Spec<? super Dependency> dependencySpec) {
-        return createCopy(CollectionUtils.filter(getAllDependencies(), dependencySpec), true);
-    }
-
-    private DefaultConfiguration createCopy(Set<Dependency> dependencies, boolean recursive) {
-        DetachedConfigurationsProvider configurationsProvider = new DetachedConfigurationsProvider();
-        DefaultConfiguration copiedConfiguration = new DefaultConfiguration(path + "Copy", name + "Copy",
-                configurationsProvider, dependencyResolver, listenerManager, metaDataProvider, resolutionStrategy);
-        configurationsProvider.setTheOnlyConfiguration(copiedConfiguration);
-        // state, cachedResolvedConfiguration, and extendsFrom intentionally not copied - must re-resolve copy
-        // copying extendsFrom could mess up dependencies when copy was re-resolved
-
-        copiedConfiguration.visibility = visibility;
-        copiedConfiguration.transitive = transitive;
-        copiedConfiguration.description = description;
-
-        copiedConfiguration.getArtifacts().addAll(getAllArtifacts());
-
-        // todo An ExcludeRule is a value object but we don't enforce immutability for DefaultExcludeRule as strong as we
-        // should (we expose the Map). We should provide a better API for ExcludeRule (I don't want to use unmodifiable Map).
-        // As soon as DefaultExcludeRule is truly immutable, we don't need to create a new instance of DefaultExcludeRule.
-        Set<Configuration> excludeRuleSources = new LinkedHashSet<Configuration>();
-        excludeRuleSources.add(this);
-        if (recursive) {
-            excludeRuleSources.addAll(getHierarchy());
-        }
-
-        for (Configuration excludeRuleSource : excludeRuleSources) {
-            for (ExcludeRule excludeRule : excludeRuleSource.getExcludeRules()) {
-                copiedConfiguration.excludeRules.add(new DefaultExcludeRule(excludeRule.getGroup(), excludeRule.getModule()));
-            }
-        }
-
-        DomainObjectSet<Dependency> copiedDependencies = copiedConfiguration.getDependencies();
-        for (Dependency dependency : dependencies) {
-            copiedDependencies.add(dependency.copy());
-        }
-        return copiedConfiguration;
-    }
-
-    public Configuration copy(Closure dependencySpec) {
-        return copy(Specs.<Dependency>convertClosureToSpec(dependencySpec));
-    }
-
-    public Configuration copyRecursive(Closure dependencySpec) {
-        return copyRecursive(Specs.<Dependency>convertClosureToSpec(dependencySpec));
-    }
-
-    public DependencyResolutionListener getDependencyResolutionBroadcast() {
-        return resolutionListenerBroadcast.getSource();
-    }
-
-    public DefaultResolutionStrategy getResolutionStrategy() {
-        return resolutionStrategy;
-    }
-
-    public Configuration resolutionStrategy(Closure closure) {
-        ConfigureUtil.configure(closure, resolutionStrategy);
-        return this;
-    }
-
-    private void throwExceptionIfNotInUnresolvedState() {
-        if (getState() != State.UNRESOLVED) {
-            throw new InvalidUserDataException("You can't change a configuration which is not in unresolved state!");
-        }
-    }
-
-    class ConfigurationFileCollection extends AbstractFileCollection {
-        private Spec<? super Dependency> dependencySpec;
-
-        private ConfigurationFileCollection(Spec<? super Dependency> dependencySpec) {
-            this.dependencySpec = dependencySpec;
-        }
-
-        public ConfigurationFileCollection(Closure dependencySpecClosure) {
-            this.dependencySpec = Specs.convertClosureToSpec(dependencySpecClosure);
-        }
-
-        public ConfigurationFileCollection(final Set<Dependency> dependencies) {
-            this.dependencySpec = new Spec<Dependency>() {
-                public boolean isSatisfiedBy(Dependency element) {
-                    return dependencies.contains(element);
-                }
-            };
-        }
-
-        @Override
-        public TaskDependency getBuildDependencies() {
-            return DefaultConfiguration.this.getBuildDependencies();
-        }
-
-        public Spec<? super Dependency> getDependencySpec() {
-            return dependencySpec;
-        }
-
-        public String getDisplayName() {
-            return String.format("%s dependencies", DefaultConfiguration.this);
-        }
-
-        public Set<File> getFiles() {
-            synchronized (lock) {
-                ResolvedConfiguration resolvedConfiguration = getResolvedConfiguration();
-                if (getState() == State.RESOLVED_WITH_FAILURES) {
-                    resolvedConfiguration.rethrowFailure();
-                }
-                return resolvedConfiguration.getFiles(dependencySpec);
-            }
-        }
-    }
-
-    /**
-     * Print a formatted representation of a Configuration
-     */
-    public String dump() {
-        StringBuilder reply = new StringBuilder();
-
-        reply.append("\nConfiguration:");
-        reply.append("  class='" + this.getClass() + "'");
-        reply.append("  name='" + this.getName() + "'");
-        reply.append("  hashcode='" + this.hashCode() + "'");
-
-        reply.append("\nLocal Dependencies:");
-        if (getDependencies().size() > 0) {
-            for (Dependency d : getDependencies()) {
-                reply.append("\n   " + d);
-            }
-        } else {
-            reply.append("\n   none");
-        }
-
-        reply.append("\nLocal Artifacts:");
-        if (getArtifacts().size() > 0) {
-            for (PublishArtifact a : getArtifacts()) {
-                reply.append("\n   " + a);
-            }
-        } else {
-            reply.append("\n   none");
-        }
-
-        reply.append("\nAll Dependencies:");
-        if (getAllDependencies().size() > 0) {
-            for (Dependency d : getAllDependencies()) {
-                reply.append("\n   " + d);
-            }
-        } else {
-            reply.append("\n   none");
-        }
-
-
-        reply.append("\nAll Artifacts:");
-        if (getAllArtifacts().size() > 0) {
-            for (PublishArtifact a : getAllArtifacts()) {
-                reply.append("\n   " + a);
-            }
-        } else {
-            reply.append("\n   none");
-        }
-
-        return reply.toString();
-    }
-
-    private class VetoContainerChangeAction implements Runnable {
-        public void run() {
-            throwExceptionIfNotInUnresolvedState();
-        }
-    }
-
-    private class ConfigurationResolvableDependencies implements ResolvableDependencies {
-        public String getName() {
-            return name;
-        }
-
-        public String getPath() {
-            return path;
-        }
-
-        @Override
-        public String toString() {
-            return String.format("dependencies '%s'", path);
-        }
-
-        public FileCollection getFiles() {
-            return DefaultConfiguration.this.fileCollection(Specs.<Dependency>satisfyAll());
-        }
-
-        public DependencySet getDependencies() {
-            return getAllDependencies();
-        }
-
-        public void beforeResolve(Action<? super ResolvableDependencies> action) {
-            resolutionListenerBroadcast.add("beforeResolve", action);
-        }
-
-        public void beforeResolve(Closure action) {
-            resolutionListenerBroadcast.add("beforeResolve", action);
-        }
-
-        public void afterResolve(Action<? super ResolvableDependencies> action) {
-            resolutionListenerBroadcast.add("afterResolve", action);
-        }
-
-        public void afterResolve(Closure action) {
-            resolutionListenerBroadcast.add("afterResolve", action);
-        }
-
-        public ResolutionResult getResolutionResult() {
-            //TODO SF unit test
-            DefaultConfiguration.this.resolveNow();
-            return DefaultConfiguration.this.cachedResolverResults.getResolutionResult();
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
deleted file mode 100644
index b49651f..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.configurations;
-
-import groovy.lang.Closure;
-import org.gradle.api.DomainObjectSet;
-import org.gradle.api.UnknownDomainObjectException;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.UnknownConfigurationException;
-import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
-import org.gradle.api.internal.DomainObjectContext;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
-import org.gradle.listener.ListenerManager;
-
-import java.util.Collection;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultConfigurationContainer extends AbstractNamedDomainObjectContainer<Configuration> 
-        implements ConfigurationContainerInternal, ConfigurationsProvider {
-    public static final String DETACHED_CONFIGURATION_DEFAULT_NAME = "detachedConfiguration";
-    
-    private final ArtifactDependencyResolver dependencyResolver;
-    private final Instantiator instantiator;
-    private final DomainObjectContext context;
-    private final ListenerManager listenerManager;
-    private final DependencyMetaDataProvider dependencyMetaDataProvider;
-
-    private int detachedConfigurationDefaultNameCounter = 1;
-
-    public DefaultConfigurationContainer(ArtifactDependencyResolver dependencyResolver,
-                                         Instantiator instantiator, DomainObjectContext context, ListenerManager listenerManager,
-                                         DependencyMetaDataProvider dependencyMetaDataProvider) {
-        super(Configuration.class, instantiator, new Configuration.Namer());
-        this.dependencyResolver = dependencyResolver;
-        this.instantiator = instantiator;
-        this.context = context;
-        this.listenerManager = listenerManager;
-        this.dependencyMetaDataProvider = dependencyMetaDataProvider;
-    }
-
-    @Override
-    protected Configuration doCreate(String name) {
-        return instantiator.newInstance(DefaultConfiguration.class, context.absoluteProjectPath(name),
-                name, this, dependencyResolver, listenerManager,
-                dependencyMetaDataProvider, new DefaultResolutionStrategy());
-    }
-
-    public Set<Configuration> getAll() {
-        return this;
-    }
-
-    public Configuration add(String name) {
-        return create(name);
-    }
-
-    public Configuration add(String name, Closure closure) {
-        return create(name, closure);
-    }
-
-    @Override
-    public ConfigurationInternal getByName(String name) {
-        return (ConfigurationInternal) super.getByName(name);
-    }
-
-    @Override
-    public String getTypeDisplayName() {
-        return "configuration";
-    }
-
-    @Override
-    protected UnknownDomainObjectException createNotFoundException(String name) {
-        return new UnknownConfigurationException(String.format("Configuration with name '%s' not found.", name));
-    }
-
-    public Configuration detachedConfiguration(Dependency... dependencies) {
-        DetachedConfigurationsProvider detachedConfigurationsProvider = new DetachedConfigurationsProvider();
-        String name = DETACHED_CONFIGURATION_DEFAULT_NAME + detachedConfigurationDefaultNameCounter++;
-        DefaultConfiguration detachedConfiguration = new DefaultConfiguration(
-                name, name, detachedConfigurationsProvider, dependencyResolver,
-                listenerManager, dependencyMetaDataProvider, new DefaultResolutionStrategy());
-        DomainObjectSet<Dependency> detachedDependencies = detachedConfiguration.getDependencies();
-        for (Dependency dependency : dependencies) {
-            detachedDependencies.add(dependency.copy());
-        }
-        detachedConfigurationsProvider.setTheOnlyConfiguration(detachedConfiguration);
-        return detachedConfiguration;
-    }
-    
-    /**
-     * Build a formatted representation of all Configurations in this ConfigurationContainer.
-     * Configuration(s) being toStringed are likely derivations of DefaultConfiguration.
-     */
-    public String dump() {
-        StringBuilder reply = new StringBuilder();
-        
-        reply.append("Configuration of type: " + getTypeDisplayName());
-        Collection<Configuration> configs = getAll();
-        for (Configuration c : configs) {
-            reply.append("\n  " + c.toString());
-        }
-        
-        return reply.toString();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultResolutionStrategy.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultResolutionStrategy.java
deleted file mode 100644
index 1b4ce95..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultResolutionStrategy.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.configurations;
-
-import org.gradle.api.artifacts.ConflictResolution;
-import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.artifacts.ResolutionStrategy;
-import org.gradle.api.artifacts.cache.ResolutionRules;
-import org.gradle.api.internal.artifacts.configurations.conflicts.LatestConflictResolution;
-import org.gradle.api.internal.artifacts.configurations.conflicts.StrictConflictResolution;
-import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy;
-import org.gradle.api.internal.artifacts.configurations.dynamicversion.DefaultCachePolicy;
-
-import java.util.LinkedHashSet;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * by Szczepan Faber, created at: 10/7/11
- */
-public class DefaultResolutionStrategy implements ResolutionStrategyInternal {
-
-    private Set<ModuleVersionSelector> forcedModules = new LinkedHashSet<ModuleVersionSelector>();
-    private ConflictResolution conflictResolution = new LatestConflictResolution();
-    private final DefaultCachePolicy cachePolicy = new DefaultCachePolicy();
-
-    public Set<ModuleVersionSelector> getForcedModules() {
-        return forcedModules;
-    }
-
-    public ResolutionStrategy failOnVersionConflict() {
-        this.conflictResolution = new StrictConflictResolution();
-        return this;
-    }
-
-    public ConflictResolution getConflictResolution() {
-        return this.conflictResolution;
-    }
-
-    public ResolutionRules getResolutionRules() {
-        return cachePolicy;
-    }
-
-    public DefaultResolutionStrategy force(Object... forcedModuleNotations) {
-        assert forcedModuleNotations != null : "forcedModuleNotations cannot be null";
-        this.forcedModules.addAll(new ForcedModuleNotationParser().parseNotation(forcedModuleNotations));
-        return this;
-    }
-
-    public DefaultResolutionStrategy setForcedModules(Object ... forcedModuleNotations) {
-        this.forcedModules = new ForcedModuleNotationParser().parseNotation(forcedModuleNotations);
-        return this;
-    }
-
-    public CachePolicy getCachePolicy() {
-        return cachePolicy;
-    }
-
-    public void cacheDynamicVersionsFor(int value, String units) {
-        TimeUnit timeUnit = TimeUnit.valueOf(units.toUpperCase());
-        cacheDynamicVersionsFor(value, timeUnit);
-    }
-
-    public void cacheDynamicVersionsFor(int value, TimeUnit units) {
-        this.cachePolicy.cacheDynamicVersionsFor(value, units);
-    }
-
-    public void cacheChangingModulesFor(int value, String units) {
-        TimeUnit timeUnit = TimeUnit.valueOf(units.toUpperCase());
-        cacheChangingModulesFor(value, timeUnit);
-    }
-
-    public void cacheChangingModulesFor(int value, TimeUnit units) {
-        this.cachePolicy.cacheChangingModulesFor(value, units);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ForcedModuleNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ForcedModuleNotationParser.java
deleted file mode 100644
index 4db406c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ForcedModuleNotationParser.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.configurations;
-
-import org.gradle.api.IllegalDependencyNotation;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
-import org.gradle.api.internal.artifacts.dsl.dependencies.ParsedModuleStringNotation;
-import org.gradle.api.internal.notations.NotationParserBuilder;
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.api.TopLevelNotationParser;
-import org.gradle.api.internal.notations.parsers.MapKey;
-import org.gradle.api.internal.notations.parsers.MapNotationParser;
-import org.gradle.api.internal.notations.parsers.TypedNotationParser;
-
-import java.util.Collection;
-import java.util.Set;
-
-/**
- * by Szczepan Faber, created at: 10/11/11
- */
-public class ForcedModuleNotationParser implements TopLevelNotationParser, NotationParser<Set<ModuleVersionSelector>> {
-
-    private NotationParser<Set<ModuleVersionSelector>> delegate = new NotationParserBuilder<ModuleVersionSelector>()
-            .resultingType(ModuleVersionSelector.class)
-            .parser(new ForcedModuleStringParser())
-            .parser(new ForcedModuleMapParser())
-            .toFlatteningComposite();
-
-    public Set<ModuleVersionSelector> parseNotation(Object notation) {
-        assert notation != null : "notation cannot be null";
-        return delegate.parseNotation(notation);
-    }
-
-    public void describe(Collection<String> candidateFormats) {
-        delegate.describe(candidateFormats);
-    }
-
-    static class ForcedModuleMapParser extends MapNotationParser<ModuleVersionSelector> {
-        @Override
-        public void describe(Collection<String> candidateFormats) {
-            candidateFormats.add("Maps, e.g. [group: 'org.gradle', name:'gradle-core', version: '1.0'].");
-        }
-
-        protected ModuleVersionSelector parseMap(@MapKey("group") String group, @MapKey("name") String name, @MapKey("version") String version) {
-            return selector(group, name, version);
-        }
-    }
-
-    static class ForcedModuleStringParser extends TypedNotationParser<CharSequence, ModuleVersionSelector> {
-
-        public ForcedModuleStringParser() {
-            super(CharSequence.class);
-        }
-
-        @Override
-        public void describe(Collection<String> candidateFormats) {
-            candidateFormats.add("Strings/CharSequences, e.g. 'org.gradle:gradle-core:1.0'.");
-        }
-
-        public ModuleVersionSelector parseType(CharSequence notation) {
-            ParsedModuleStringNotation parsed;
-            try {
-                parsed = new ParsedModuleStringNotation(notation.toString(), null);
-            } catch (IllegalDependencyNotation e) {
-                throw new InvalidUserDataException(
-                    "Invalid format: '" + notation + "'. The Correct notation is a 3-part group:name:version notation,"
-                    + "e.g: 'org.gradle:gradle-core:1.0'");
-            }
-
-            if (parsed.getGroup() == null || parsed.getName() == null || parsed.getVersion() == null) {
-                throw new InvalidUserDataException(
-                    "Invalid format: '" + notation + "'. Group, name and version cannot be empty. Correct example: "
-                    + "'org.gradle:gradle-core:1.0'");
-            }
-            return selector(parsed.getGroup(), parsed.getName(), parsed.getVersion());
-        }
-    }
-
-    static ModuleVersionSelector selector(final String group, final String name, final String version) {
-        return new DefaultModuleVersionSelector(group, name, version);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ResolutionStrategyInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ResolutionStrategyInternal.java
index 9f19950..9ece2d4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ResolutionStrategyInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ResolutionStrategyInternal.java
@@ -15,12 +15,15 @@
  */
 package org.gradle.api.internal.artifacts.configurations;
 
+import org.gradle.api.Action;
 import org.gradle.api.artifacts.ConflictResolution;
 import org.gradle.api.artifacts.ResolutionStrategy;
 import org.gradle.api.artifacts.cache.ResolutionRules;
+import org.gradle.api.internal.artifacts.DependencyResolveDetailsInternal;
 import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy;
 
 public interface ResolutionStrategyInternal extends ResolutionStrategy {
+
     /**
      * Gets the current expiry policy for dynamic revisions.
      *
@@ -40,4 +43,14 @@ public interface ResolutionStrategyInternal extends ResolutionStrategy {
      * @return the resolution rules
      */
     ResolutionRules getResolutionRules();
+
+    /**
+     * @return the dependency resolve rule (may aggregate multiple rules)
+     */
+    Action<DependencyResolveDetailsInternal> getDependencyResolveRule();
+
+    /**
+     * @return copy of this resolution strategy. See the contract of {@link org.gradle.api.artifacts.Configuration#copy()}.
+     */
+    ResolutionStrategyInternal copy();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/conflicts/LatestConflictResolution.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/conflicts/LatestConflictResolution.java
deleted file mode 100644
index 603438c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/conflicts/LatestConflictResolution.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.configurations.conflicts;
-
-import org.gradle.api.artifacts.ConflictResolution;
-
-/**
- * Latest resolution strategy
- * <p>
- * by Szczepan Faber, created at: 10/5/11
- */
-public class LatestConflictResolution implements ConflictResolution {
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/conflicts/StrictConflictResolution.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/conflicts/StrictConflictResolution.java
deleted file mode 100644
index 1fdbacb..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/conflicts/StrictConflictResolution.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.configurations.conflicts;
-
-import org.gradle.api.artifacts.ConflictResolution;
-
-/**
- * Strict type, allows configuring (forcing) certain dependency versions using dependency notation
- * <p>
- * by Szczepan Faber, created at: 10/5/11
- */
-public class StrictConflictResolution implements ConflictResolution {
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/CachePolicy.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/CachePolicy.java
index b65975b..f2cad6e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/CachePolicy.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/CachePolicy.java
@@ -30,5 +30,5 @@ public interface CachePolicy {
 
     boolean mustRefreshChangingModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion resolvedModuleVersion, long ageMillis);
 
-    boolean mustRefreshArtifact(ArtifactIdentifier artifactIdentifier, File cachedArtifactFile, long ageMillis);
+    boolean mustRefreshArtifact(ArtifactIdentifier artifactIdentifier, File cachedArtifactFile, long ageMillis, boolean belongsToChangingModule, boolean moduleDescriptorInSync);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/DefaultCachePolicy.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/DefaultCachePolicy.java
deleted file mode 100644
index 68b3509..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/DefaultCachePolicy.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.configurations.dynamicversion;
-
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.Action;
-import org.gradle.api.artifacts.ArtifactIdentifier;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.artifacts.ResolvedModuleVersion;
-import org.gradle.api.artifacts.cache.*;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-public class DefaultCachePolicy implements CachePolicy, ResolutionRules {
-    private static final int SECONDS_IN_DAY = 24 * 60 * 60;
-
-    private final List<Action<? super DependencyResolutionControl>> dependencyCacheRules = new ArrayList<Action<? super DependencyResolutionControl>>();
-    private final List<Action<? super ModuleResolutionControl>> moduleCacheRules = new ArrayList<Action<? super ModuleResolutionControl>>();
-    private final List<Action<? super ArtifactResolutionControl>> artifactCacheRules = new ArrayList<Action<? super ArtifactResolutionControl>>();
-
-    public DefaultCachePolicy() {
-        cacheDynamicVersionsFor(SECONDS_IN_DAY, TimeUnit.SECONDS);
-        cacheChangingModulesFor(SECONDS_IN_DAY, TimeUnit.SECONDS);
-        cacheMissingModulesAndArtifactsFor(SECONDS_IN_DAY, TimeUnit.SECONDS);
-    }
-
-    public void eachDependency(Action<? super DependencyResolutionControl> rule) {
-        dependencyCacheRules.add(0, rule);
-    }
-
-    public void eachModule(Action<? super ModuleResolutionControl> rule) {
-        moduleCacheRules.add(0, rule);
-    }
-
-    public void eachArtifact(Action<? super ArtifactResolutionControl> rule) {
-        artifactCacheRules.add(0, rule);
-    }
-
-    public void cacheDynamicVersionsFor(final int value, final TimeUnit unit) {
-        eachDependency(new Action<DependencyResolutionControl>() {
-            public void execute(DependencyResolutionControl dependencyResolutionControl) {
-                dependencyResolutionControl.cacheFor(value, unit);
-            }
-        });
-    }
-
-    public void cacheChangingModulesFor(final int value, final TimeUnit units) {
-        eachModule(new Action<ModuleResolutionControl>() {
-            public void execute(ModuleResolutionControl moduleResolutionControl) {
-                if (moduleResolutionControl.isChanging()) {
-                    moduleResolutionControl.cacheFor(value, units);
-                }
-            }
-        });
-    }
-
-    private void cacheMissingModulesAndArtifactsFor(final int value, final TimeUnit units) {
-        eachModule(new Action<ModuleResolutionControl>() {
-            public void execute(ModuleResolutionControl moduleResolutionControl) {
-                if (moduleResolutionControl.getCachedResult() == null) {
-                    moduleResolutionControl.cacheFor(value, units);
-                }
-            }
-        });
-        eachArtifact(new Action<ArtifactResolutionControl>() {
-            public void execute(ArtifactResolutionControl artifactResolutionControl) {
-                if (artifactResolutionControl.getCachedResult() == null) {
-                    artifactResolutionControl.cacheFor(value, units);
-                }
-            }
-        });
-    }
-
-    public boolean mustRefreshDynamicVersion(ModuleVersionSelector selector, ModuleVersionIdentifier moduleId, long ageMillis) {
-        CachedDependencyResolutionControl dependencyResolutionControl = new CachedDependencyResolutionControl(selector, moduleId, ageMillis);
-
-        for (Action<? super DependencyResolutionControl> rule : dependencyCacheRules) {
-            rule.execute(dependencyResolutionControl);
-            if (dependencyResolutionControl.ruleMatch()) {
-                return dependencyResolutionControl.mustCheck();
-            }
-        }
-
-        return false;
-    }
-
-    public boolean mustRefreshModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion resolvedModuleVersion, ModuleRevisionId moduleRevisionId, final long ageMillis) {
-        return mustRefreshModule(moduleVersionId, resolvedModuleVersion, ageMillis, false);
-    }
-
-    public boolean mustRefreshChangingModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion resolvedModuleVersion, long ageMillis) {
-        return mustRefreshModule(moduleVersionId, resolvedModuleVersion, ageMillis, true);
-    }
-
-    private boolean mustRefreshModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion version, long ageMillis, boolean changingModule) {
-        CachedModuleResolutionControl moduleResolutionControl = new CachedModuleResolutionControl(moduleVersionId, version, changingModule, ageMillis);
-
-        for (Action<? super ModuleResolutionControl> rule : moduleCacheRules) {
-            rule.execute(moduleResolutionControl);
-            if (moduleResolutionControl.ruleMatch()) {
-                return moduleResolutionControl.mustCheck();
-            }
-        }
-
-        return false;
-    }
-
-    public boolean mustRefreshArtifact(ArtifactIdentifier artifactIdentifier, File cachedArtifactFile, long ageMillis) {
-        CachedArtifactResolutionControl artifactResolutionControl = new CachedArtifactResolutionControl(artifactIdentifier, cachedArtifactFile, ageMillis);
-
-        for (Action<? super ArtifactResolutionControl> rule : artifactCacheRules) {
-            rule.execute(artifactResolutionControl);
-            if (artifactResolutionControl.ruleMatch()) {
-                return artifactResolutionControl.mustCheck();
-            }
-        }
-
-        return false;
-    }
-
-    private abstract static class AbstractResolutionControl<A, B> implements ResolutionControl<A, B> {
-        private final A request;
-        private final B cachedResult;
-        private final long ageMillis;
-        private boolean ruleMatch;
-        private boolean mustCheck;
-
-        private AbstractResolutionControl(A request, B cachedResult, long ageMillis) {
-            this.request = request;
-            this.cachedResult = cachedResult;
-            this.ageMillis = ageMillis;
-        }
-
-        public A getRequest() {
-            return request;
-        }
-
-        public B getCachedResult() {
-            return cachedResult;
-        }
-
-        public void cacheFor(int value, TimeUnit units) {
-            long timeoutMillis = TimeUnit.MILLISECONDS.convert(value, units);
-            if (ageMillis <= timeoutMillis) {
-                setMustCheck(false);
-            } else {
-                setMustCheck(true);
-            }
-        }
-
-        public void useCachedResult() {
-            setMustCheck(false);
-        }
-
-        public void refresh() {
-            setMustCheck(true);
-        }
-
-        private void setMustCheck(boolean val) {
-            ruleMatch = true;
-            mustCheck = val;
-        }
-
-        public boolean ruleMatch() {
-            return ruleMatch;
-        }
-
-        public boolean mustCheck() {
-            return mustCheck;
-        }
-    }
-
-    private class CachedDependencyResolutionControl extends AbstractResolutionControl<ModuleVersionSelector, ModuleVersionIdentifier> implements DependencyResolutionControl {
-        private CachedDependencyResolutionControl(ModuleVersionSelector request, ModuleVersionIdentifier cachedVersion, long ageMillis) {
-            super(request, cachedVersion, ageMillis);
-        }
-    }
-
-    private class CachedModuleResolutionControl extends AbstractResolutionControl<ModuleVersionIdentifier, ResolvedModuleVersion> implements ModuleResolutionControl {
-        private final boolean changing;
-
-        private CachedModuleResolutionControl(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion cachedVersion, boolean changing, long ageMillis) {
-            super(moduleVersionId, cachedVersion, ageMillis);
-            this.changing = changing;
-        }
-
-        public boolean isChanging() {
-            return changing;
-        }
-    }
-
-    private class CachedArtifactResolutionControl extends AbstractResolutionControl<ArtifactIdentifier, File> implements ArtifactResolutionControl {
-        private CachedArtifactResolutionControl(ArtifactIdentifier artifactIdentifier, File cachedResult, long ageMillis) {
-            super(artifactIdentifier, cachedResult, ageMillis);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependency.java
index 90d490a..09c136f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependency.java
@@ -19,10 +19,10 @@ package org.gradle.api.internal.artifacts.dependencies;
 import org.gradle.api.Project;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
 import org.gradle.api.artifacts.ProjectDependency;
 import org.gradle.api.internal.artifacts.CachingDependencyResolveContext;
 import org.gradle.api.internal.artifacts.DependencyResolveContext;
+import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.AbstractTaskDependency;
 import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
@@ -155,6 +155,7 @@ public class DefaultProjectDependency extends AbstractModuleDependency implement
             if (!instruction.isRebuild()) {
                 return;
             }
+            dependencyProject.ensureEvaluated();
             Configuration configuration = getProjectConfiguration();
             context.add(configuration);
             context.add(configuration.getAllArtifacts());
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryFactory.java
deleted file mode 100644
index 9be55b4..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryFactory.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.dsl;
-
-import org.gradle.api.Action;
-import org.gradle.api.artifacts.repositories.ArtifactRepository;
-import org.gradle.api.artifacts.repositories.FlatDirectoryArtifactRepository;
-import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
-import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.api.internal.artifacts.BaseRepositoryFactory;
-import org.gradle.util.ConfigureUtil;
-import org.gradle.util.GUtil;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.gradle.api.artifacts.ArtifactRepositoryContainer.DEFAULT_MAVEN_CENTRAL_REPO_NAME;
-import static org.gradle.api.artifacts.ArtifactRepositoryContainer.DEFAULT_MAVEN_LOCAL_REPO_NAME;
-import static org.gradle.util.CollectionUtils.flattenToList;
-
-public class DefaultRepositoryFactory implements RepositoryFactoryInternal {
-
-    public static final String FLAT_DIR_DEFAULT_NAME = "flatDir";
-    private static final String MAVEN_REPO_DEFAULT_NAME = "maven";
-    private static final String IVY_REPO_DEFAULT_NAME = "ivy";
-
-    private final BaseRepositoryFactory baseRepositoryFactory;
-
-    public DefaultRepositoryFactory(BaseRepositoryFactory baseRepositoryFactory) {
-        this.baseRepositoryFactory = baseRepositoryFactory;
-    }
-
-    public BaseRepositoryFactory getBaseRepositoryFactory() {
-        return baseRepositoryFactory;
-    }
-
-    public FlatDirectoryArtifactRepository flatDir(Action<? super FlatDirectoryArtifactRepository> action) {
-        return configure(getBaseRepositoryFactory().createFlatDirRepository(), action, FLAT_DIR_DEFAULT_NAME);
-    }
-
-    public FlatDirectoryArtifactRepository flatDir(Map<String, ?> args) {
-        Map<String, Object> modifiedArgs = new HashMap<String, Object>(args);
-        if (modifiedArgs.containsKey("dirs")) {
-            modifiedArgs.put("dirs", flattenToList(modifiedArgs.get("dirs")));
-        }
-
-        return configure(getBaseRepositoryFactory().createFlatDirRepository(), modifiedArgs, FLAT_DIR_DEFAULT_NAME);
-    }
-
-    public MavenArtifactRepository mavenLocal() {
-        return configure(baseRepositoryFactory.createMavenLocalRepository(), DEFAULT_MAVEN_LOCAL_REPO_NAME);
-    }
-
-    public MavenArtifactRepository mavenCentral() {
-        return configure(baseRepositoryFactory.createMavenCentralRepository(), DEFAULT_MAVEN_CENTRAL_REPO_NAME);
-    }
-
-    public MavenArtifactRepository maven(Action<? super MavenArtifactRepository> action) {
-        return configure(baseRepositoryFactory.createMavenRepository(), action, MAVEN_REPO_DEFAULT_NAME);
-    }
-
-    public IvyArtifactRepository ivy(Action<? super IvyArtifactRepository> action) {
-        return configure(baseRepositoryFactory.createIvyRepository(), action, IVY_REPO_DEFAULT_NAME);
-    }
-
-    private <T extends ArtifactRepository> T configure(T repository, Action<? super T> action, String name) {
-        action.execute(repository);
-        return configure(repository, name);
-    }
-
-    private <T extends ArtifactRepository> T configure(T repository, Map<String, ?> properties, String name) {
-        ConfigureUtil.configureByMap(properties, repository);
-        return configure(repository, name);
-    }
-
-    private <T extends ArtifactRepository> T configure(T repository, String name) {
-        String repositoryName = repository.getName();
-        if (!GUtil.isTrue(repositoryName)) {
-            repository.setName(name);
-        }
-        return repository;
-    }
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java
index 387f5fc..8474ef5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java
@@ -22,8 +22,9 @@ import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.artifacts.repositories.FlatDirectoryArtifactRepository;
 import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.api.internal.Actions;
 import org.gradle.api.internal.ClosureBackedAction;
+import org.gradle.api.internal.ConfigureByMapAction;
+import org.gradle.api.internal.artifacts.BaseRepositoryFactory;
 import org.gradle.api.internal.artifacts.DefaultArtifactRepositoryContainer;
 import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
 import org.gradle.api.internal.artifacts.repositories.FixedResolverArtifactRepository;
@@ -42,15 +43,19 @@ import static org.gradle.util.CollectionUtils.flattenToList;
  */
 public class DefaultRepositoryHandler extends DefaultArtifactRepositoryContainer implements RepositoryHandler, ResolverProvider {
 
-    private final RepositoryFactoryInternal repositoryFactory;
+    public static final String FLAT_DIR_DEFAULT_NAME = "flatDir";
+    private static final String MAVEN_REPO_DEFAULT_NAME = "maven";
+    private static final String IVY_REPO_DEFAULT_NAME = "ivy";
 
-    public DefaultRepositoryHandler(RepositoryFactoryInternal repositoryFactory, Instantiator instantiator) {
-        super(repositoryFactory.getBaseRepositoryFactory(), instantiator);
+    private final BaseRepositoryFactory repositoryFactory;
+
+    public DefaultRepositoryHandler(BaseRepositoryFactory repositoryFactory, Instantiator instantiator) {
+        super(repositoryFactory, instantiator);
         this.repositoryFactory = repositoryFactory;
     }
 
     public FlatDirectoryArtifactRepository flatDir(Action<? super FlatDirectoryArtifactRepository> action) {
-        return addRepository(repositoryFactory.flatDir(action));
+        return addRepository(repositoryFactory.createFlatDirRepository(), FLAT_DIR_DEFAULT_NAME, action);
     }
 
     public FlatDirectoryArtifactRepository flatDir(Closure configureClosure) {
@@ -58,11 +63,15 @@ public class DefaultRepositoryHandler extends DefaultArtifactRepositoryContainer
     }
 
     public FlatDirectoryArtifactRepository flatDir(Map<String, ?> args) {
-        return addRepository(repositoryFactory.flatDir(args));
+        Map<String, Object> modifiedArgs = new HashMap<String, Object>(args);
+        if (modifiedArgs.containsKey("dirs")) {
+            modifiedArgs.put("dirs", flattenToList(modifiedArgs.get("dirs")));
+        }
+        return flatDir(new ConfigureByMapAction<FlatDirectoryArtifactRepository>(modifiedArgs));
     }
 
     public MavenArtifactRepository mavenCentral() {
-        return addRepository(repositoryFactory.mavenCentral());
+        return addRepository(repositoryFactory.createMavenCentralRepository(), DEFAULT_MAVEN_CENTRAL_REPO_NAME);
     }
 
     public MavenArtifactRepository mavenCentral(Map<String, ?> args) {
@@ -76,13 +85,11 @@ public class DefaultRepositoryHandler extends DefaultArtifactRepositoryContainer
             modifiedArgs.put("artifactUrls", urls);
         }
 
-        MavenArtifactRepository repo = repositoryFactory.mavenCentral();
-        ConfigureUtil.configureByMap(modifiedArgs, repo);
-        return addRepository(repo);
+        return addRepository(repositoryFactory.createMavenCentralRepository(), DEFAULT_MAVEN_CENTRAL_REPO_NAME, new ConfigureByMapAction<MavenArtifactRepository>(modifiedArgs));
     }
 
     public MavenArtifactRepository mavenLocal() {
-        return addRepository(repositoryFactory.mavenLocal());
+        return addRepository(repositoryFactory.createMavenLocalRepository(), DEFAULT_MAVEN_LOCAL_REPO_NAME);
     }
 
     public DependencyResolver mavenRepo(Map<String, ?> args) {
@@ -104,17 +111,17 @@ public class DefaultRepositoryHandler extends DefaultArtifactRepositoryContainer
             }
         }
 
-        MavenArtifactRepository repository = repositoryFactory.maven(Actions.doNothing());
+        MavenArtifactRepository repository = repositoryFactory.createMavenRepository();
         ConfigureUtil.configureByMap(modifiedArgs, repository);
-        DependencyResolver resolver = repositoryFactory.getBaseRepositoryFactory().toResolver(repository);
+        DependencyResolver resolver = repositoryFactory.toResolver(repository);
         ConfigureUtil.configure(configClosure, resolver);
-        addRepository(new FixedResolverArtifactRepository(resolver));
+        addRepository(new FixedResolverArtifactRepository(resolver), "mavenRepo");
         return resolver;
     }
 
 
     public MavenArtifactRepository maven(Action<? super MavenArtifactRepository> action) {
-        return addRepository(repositoryFactory.maven(action));
+        return addRepository(repositoryFactory.createMavenRepository(), MAVEN_REPO_DEFAULT_NAME, action);
     }
 
     public MavenArtifactRepository maven(Closure closure) {
@@ -122,11 +129,12 @@ public class DefaultRepositoryHandler extends DefaultArtifactRepositoryContainer
     }
 
     public IvyArtifactRepository ivy(Action<? super IvyArtifactRepository> action) {
-        return addRepository(repositoryFactory.ivy(action));
+        return addRepository(repositoryFactory.createIvyRepository(), IVY_REPO_DEFAULT_NAME, action);
     }
 
     public IvyArtifactRepository ivy(Closure closure) {
         return ivy(new ClosureBackedAction<IvyArtifactRepository>(closure));
     }
 
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/RepositoryFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/RepositoryFactory.java
deleted file mode 100644
index bc09926..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/RepositoryFactory.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.dsl;
-
-import org.gradle.api.Action;
-import org.gradle.api.artifacts.repositories.FlatDirectoryArtifactRepository;
-import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
-import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-
-import java.util.Map;
-
-/**
- * A repository factory is capable of creating different kinds of {@link org.gradle.api.artifacts.repositories.ArtifactRepository} types.
- */
-public interface RepositoryFactory {
-
-    /*
-        Internal note: Not everything was copied from RepositoryHandler, only what wasn't going to be deprecated eventually.
-     */
-
-    /**
-     * Creates a repository that looks into a number of directories for artifacts. The artifacts are expected to be located in the
-     * root of the specified directories. The repository ignores any group/organization information specified in the
-     * dependency section of your build script. If you only use this kind of repository you might specify your
-     * dependencies like <code>":junit:4.4"</code> instead of <code>"junit:junit:4.4"</code>.
-     *
-     * The following parameter are accepted as keys for the map:
-     *
-     * <table summary="Shows property keys and associated values">
-     * <tr><th>Key</th>
-     *     <th>Description of Associated Value</th></tr>
-     * <tr><td><code>name</code></td>
-     *     <td><em>(optional)</em> The name of the repository.
-     * The default is a Hash value of the rootdir paths. The name is used in the console output,
-     * to point to information related to a particular repository. A name must be unique amongst a repository group.</td></tr>
-     * <tr><td><code>dirs</code></td>
-     *     <td>Specifies a list of rootDirs where to look for dependencies. These are evaluated as for {@link org.gradle.api.Project#files(Object...)}</td></tr>
-     * </table>
-     *
-     * <p>Examples:
-     * <pre>
-     * repositories {
-     *     flatDir name: 'libs', dirs: "$projectDir/libs"
-     *     flatDir dirs: ["$projectDir/libs1", "$projectDir/libs2"]
-     * }
-     * </pre>
-     * </p>
-     *
-     * @param args The arguments used to configure the repository.
-     * @return the created repository
-     * @throws org.gradle.api.InvalidUserDataException In the case neither rootDir nor rootDirs is specified of if both
-     * are specified.
-     */
-    FlatDirectoryArtifactRepository flatDir(Map<String, ?> args);
-
-    /**
-     * Creates and configures a repository which will look for dependencies in a number of local directories.
-     *
-     * @param action The action to execute to configure the repository.
-     * @return The repository.
-     */
-    FlatDirectoryArtifactRepository flatDir(Action<? super FlatDirectoryArtifactRepository> action);
-
-    /**
-     * Creates a repository which looks in the Maven central repository for dependencies. The URL used to access this repository is
-     * {@value org.gradle.api.artifacts.ArtifactRepositoryContainer#MAVEN_CENTRAL_URL}. The name of the repository is
-     * {@value org.gradle.api.artifacts.ArtifactRepositoryContainer#DEFAULT_MAVEN_CENTRAL_REPO_NAME}.
-     *
-     * @return the created repository
-     */
-    MavenArtifactRepository mavenCentral();
-
-    /**
-     * Creates a repository which looks in the local Maven cache for dependencies. The name of the repository is
-     * {@value org.gradle.api.artifacts.ArtifactRepositoryContainer#DEFAULT_MAVEN_LOCAL_REPO_NAME}.
-     *
-     * @return the created repository
-     */
-    MavenArtifactRepository mavenLocal();
-
-    /**
-     * Creates and configures a Maven repository.
-     *
-     * @param action The action to use to configure the repository.
-     * @return The created repository.
-     */
-    MavenArtifactRepository maven(Action<? super MavenArtifactRepository> action);
-
-    /**
-     * Creates and configures an Ivy repository.
-     *
-     * @param action The action to use to configure the repository.
-     * @return The created repository.
-     */
-    IvyArtifactRepository ivy(Action<? super IvyArtifactRepository> action);
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/RepositoryFactoryInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/RepositoryFactoryInternal.java
deleted file mode 100644
index 5667d8b..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/RepositoryFactoryInternal.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.dsl;
-
-import org.gradle.api.internal.artifacts.BaseRepositoryFactory;
-
-public interface RepositoryFactoryInternal extends RepositoryFactory {
-
-    BaseRepositoryFactory getBaseRepositoryFactory();
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ParsedModuleStringNotation.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ParsedModuleStringNotation.java
deleted file mode 100644
index 88b19bf..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ParsedModuleStringNotation.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.dsl.dependencies;
-
-import org.gradle.api.IllegalDependencyNotation;
-import org.gradle.util.GUtil;
-
-/**
- * @author Hans Dockter
- */
-public class ParsedModuleStringNotation {
-    private String group;
-    private String name;
-    private String version;
-    private String classifier;
-    private String artifactType;
-
-    public ParsedModuleStringNotation(String moduleNotation, String artifactType) {
-        assignValuesFromModuleNotation(moduleNotation);
-        this.artifactType = artifactType;
-    }
-
-    private void assignValuesFromModuleNotation(String moduleNotation) {
-        String[] moduleNotationParts = moduleNotation.split(":");
-        if (moduleNotationParts.length < 2 || moduleNotationParts.length > 4) {
-            throw new IllegalDependencyNotation("The description " + moduleNotation + " is invalid");
-        }
-        group = GUtil.elvis(moduleNotationParts[0], null);
-        name = moduleNotationParts[1];
-        version = moduleNotationParts.length == 2 ? null : moduleNotationParts[2];
-        if (moduleNotationParts.length == 4) {
-            classifier = moduleNotationParts[3];
-        }
-    }
-
-    public String getGroup() {
-        return group;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public String getVersion() {
-        return version;
-    }
-
-    public String getClassifier() {
-        return classifier;
-    }
-
-    public String getArtifactType() {
-        return artifactType;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.java
new file mode 100644
index 0000000..d95ffa3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.internal.xml.XmlTransformer;
+
+import java.io.File;
+
+public interface IvyModuleDescriptorWriter {
+
+    public void write(ModuleDescriptor md, File output);
+
+    public void write(ModuleDescriptor md, File output, XmlTransformer descriptorModifier);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java
new file mode 100644
index 0000000..c706a2e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2007-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ModuleDescriptorConverter {
+    ModuleDescriptor convert(Set<? extends Configuration> configurations, Module module);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifact.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifact.java
index b83b7a5..eb4fa9a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifact.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifact.java
@@ -90,4 +90,5 @@ public class ArchivePublishArtifact extends AbstractPublishArtifact {
     public void setFile(File file) {
         this.file = file;
     }
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/result/ResolvedDependencyResultPrinter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/result/ResolvedDependencyResultPrinter.java
deleted file mode 100644
index e788d44..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/result/ResolvedDependencyResultPrinter.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.result;
-
-import org.gradle.api.artifacts.result.ResolvedDependencyResult;
-
-/**
- * Created: 17/08/2012
- *
- * @author Szczepan Faber
- */
-public class ResolvedDependencyResultPrinter {
-
-    public static String print(ResolvedDependencyResult result) {
-        if (!result.getRequested().matchesStrictly(result.getSelected().getId())) {
-            return requested(result) + " -> " + result.getSelected().getId().getVersion();
-        } else {
-            return requested(result);
-        }
-    }
-
-    private static String requested(ResolvedDependencyResult result) {
-        return result.getRequested().getGroup() + ":" + result.getRequested().getName() + ":" + result.getRequested().getVersion();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheBackedTaskHistoryRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheBackedTaskHistoryRepository.java
index 247daec..ac68218 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheBackedTaskHistoryRepository.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheBackedTaskHistoryRepository.java
@@ -17,7 +17,7 @@ package org.gradle.api.internal.changedetection;
 
 import org.gradle.internal.Factory;
 import org.gradle.api.internal.TaskInternal;
-import org.gradle.cache.DefaultSerializer;
+import org.gradle.messaging.serialize.DefaultSerializer;
 import org.gradle.cache.PersistentIndexedCache;
 
 import java.io.File;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CachingHasher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CachingHasher.java
index 1b57aee..f779d8d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CachingHasher.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CachingHasher.java
@@ -16,7 +16,7 @@
 package org.gradle.api.internal.changedetection;
 
 import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.messaging.serialize.Serializer;
+import org.gradle.messaging.serialize.DataStreamBackedSerializer;
 
 import java.io.*;
 
@@ -56,9 +56,9 @@ public class CachingHasher implements Hasher {
         }
     }
 
-    private static class FileInfoSerializer implements Serializer<FileInfo> {
-        public FileInfo read(InputStream instr) throws Exception {
-            DataInputStream input = new DataInputStream(instr);
+    private static class FileInfoSerializer extends DataStreamBackedSerializer<FileInfo> {
+        @Override
+        public FileInfo read(DataInput input) throws IOException {
             int hashLength = input.readInt();
             byte[] hash = new byte[hashLength];
             input.readFully(hash);
@@ -67,13 +67,12 @@ public class CachingHasher implements Hasher {
             return new FileInfo(hash, length, timestamp);
         }
 
-        public void write(OutputStream outstr, FileInfo value) throws Exception {
-            DataOutputStream output = new DataOutputStream(outstr);
+        @Override
+        public void write(DataOutput output, FileInfo value) throws IOException {
             output.writeInt(value.hash.length);
             output.write(value.hash);
             output.writeLong(value.timestamp);
             output.writeLong(value.length);
-            output.flush();
         }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/InMemoryIndexedCache.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/InMemoryIndexedCache.java
index 3d02525..e03e6c3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/InMemoryIndexedCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/InMemoryIndexedCache.java
@@ -15,11 +15,12 @@
  */
 package org.gradle.api.internal.changedetection;
 
-import org.gradle.api.UncheckedIOException;
 import org.gradle.cache.PersistentIndexedCache;
 import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.serialize.Serializer;
 
-import java.io.*;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -27,7 +28,12 @@ import java.util.Map;
  * A simple in-memory cache, used by the testing fixtures.
  */
 public class InMemoryIndexedCache<K, V> implements PersistentIndexedCache<K, V> {
-    Map<Object, byte[]> entries = new HashMap<Object, byte[]>();
+    private final Map<Object, byte[]> entries = new HashMap<Object, byte[]>();
+    private final Serializer<V> valueSerializer;
+
+    public InMemoryIndexedCache(Serializer<V> valueSerializer) {
+        this.valueSerializer = valueSerializer;
+    }
 
     public V get(K key) {
         byte[] serialised = entries.get(key);
@@ -36,7 +42,7 @@ public class InMemoryIndexedCache<K, V> implements PersistentIndexedCache<K, V>
         }
         try {
             ByteArrayInputStream instr = new ByteArrayInputStream(serialised);
-            return (V)new ObjectInputStream(instr).readObject();
+            return valueSerializer.read(instr);
         } catch (Exception e) {
             throw UncheckedException.throwAsUncheckedException(e);
         }
@@ -45,11 +51,9 @@ public class InMemoryIndexedCache<K, V> implements PersistentIndexedCache<K, V>
     public void put(K key, V value) {
         ByteArrayOutputStream outstr = new ByteArrayOutputStream();
         try {
-            ObjectOutputStream objstr = new ObjectOutputStream(outstr);
-            objstr.writeObject(value);
-            objstr.close();
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
+            valueSerializer.write(outstr, value);
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
         }
 
         entries.put(key, outstr.toByteArray());
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/component/DefaultSoftwareComponentContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/component/DefaultSoftwareComponentContainer.java
new file mode 100644
index 0000000..54999f1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/component/DefaultSoftwareComponentContainer.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.component;
+
+import org.gradle.api.component.SoftwareComponent;
+import org.gradle.api.component.SoftwareComponentContainer;
+import org.gradle.api.internal.DefaultNamedDomainObjectSet;
+import org.gradle.internal.reflect.Instantiator;
+
+public class DefaultSoftwareComponentContainer extends DefaultNamedDomainObjectSet<SoftwareComponent> implements SoftwareComponentContainer {
+    public DefaultSoftwareComponentContainer(Instantiator instantiator) {
+        super(SoftwareComponentInternal.class, instantiator);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/component/SoftwareComponentInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/component/SoftwareComponentInternal.java
new file mode 100644
index 0000000..4fd702b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/component/SoftwareComponentInternal.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.component;
+
+import org.gradle.api.artifacts.DependencySet;
+import org.gradle.api.artifacts.PublishArtifactSet;
+import org.gradle.api.component.SoftwareComponent;
+
+public interface SoftwareComponentInternal extends SoftwareComponent {
+    PublishArtifactSet getArtifacts();
+    DependencySet getRuntimeDependencies();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileTreeElement.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileTreeElement.java
index fb2b901..32c3744 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileTreeElement.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileTreeElement.java
@@ -22,6 +22,7 @@ import org.gradle.api.file.FileTreeElement;
 import org.gradle.internal.nativeplatform.filesystem.Chmod;
 import org.gradle.internal.nativeplatform.filesystem.FileSystem;
 import org.gradle.internal.nativeplatform.filesystem.FileSystems;
+import org.gradle.util.GFileUtils;
 
 import java.io.*;
 
@@ -57,10 +58,10 @@ public abstract class AbstractFileTreeElement implements FileTreeElement {
     public boolean copyTo(File target) {
         validateTimeStamps();
         try {
-            target.getParentFile().mkdirs();
             if (isDirectory()) {
-                target.mkdirs();
+                GFileUtils.mkdirs(target);
             } else {
+                GFileUtils.mkdirs(target.getParentFile());
                 copyFile(target);
             }
             getChmod().chmod(target, getMode());
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileOperations.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileOperations.java
index 1c90179..7a19cbf 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileOperations.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileOperations.java
@@ -36,6 +36,7 @@ import org.gradle.process.internal.DefaultJavaExecAction;
 import org.gradle.process.internal.ExecAction;
 import org.gradle.process.internal.JavaExecAction;
 import org.gradle.util.ConfigureUtil;
+import org.gradle.util.GFileUtils;
 
 import java.io.File;
 import java.net.URI;
@@ -120,7 +121,7 @@ public class DefaultFileOperations implements FileOperations, ProcessOperations
         if (dir.isFile()) {
             throw new InvalidUserDataException(String.format("Can't create directory. The path=%s points to an existing file.", path));
         }
-        dir.mkdirs();
+        GFileUtils.mkdirs(dir);
         return dir;
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileVisitDetails.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileVisitDetails.java
new file mode 100644
index 0000000..2cbccbd
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileVisitDetails.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file;
+
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.RelativePath;
+
+import java.io.File;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class DefaultFileVisitDetails extends DefaultFileTreeElement implements FileVisitDetails {
+    private final AtomicBoolean stop;
+
+    public DefaultFileVisitDetails(File file, RelativePath relativePath, AtomicBoolean stop) {
+        super(file, relativePath);
+        this.stop = stop;
+    }
+
+    public void stopVisiting() {
+        stop.set(true);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProvider.java
index 71b343d..5e0d564 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProvider.java
@@ -38,7 +38,7 @@ public class DefaultTemporaryFileProvider implements TemporaryFileProvider {
 
     public File createTemporaryFile(String prefix, @Nullable String suffix, String... path) {
         File dir = new File(baseDirFactory.create(), CollectionUtils.join("/", path));
-        GFileUtils.createDirectory(dir);
+        GFileUtils.mkdirs(dir);
         try {
             return File.createTempFile(prefix, suffix, dir);
         } catch (IOException e) {
@@ -48,7 +48,7 @@ public class DefaultTemporaryFileProvider implements TemporaryFileProvider {
 
     public File createTemporaryDirectory(@Nullable String prefix, @Nullable String suffix, @Nullable String... path) {
         File dir = new File(baseDirFactory.create(), CollectionUtils.join("/", path));
-        GFileUtils.createDirectory(dir);
+        GFileUtils.mkdirs(dir);
         try {
             // TODO: This is not a great paradigm for creating a temporary directory.
             // See http://guava-libraries.googlecode.com/svn/tags/release08/javadoc/com/google/common/io/Files.html#createTempDir%28%29 for an alternative.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DirectoryFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DirectoryFileTree.java
index 7818e6c..157cb4e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DirectoryFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DirectoryFileTree.java
@@ -19,6 +19,7 @@ package org.gradle.api.internal.file.collections;
 import org.gradle.api.GradleException;
 import org.gradle.api.file.*;
 import org.gradle.api.internal.file.DefaultFileTreeElement;
+import org.gradle.api.internal.file.DefaultFileVisitDetails;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.api.specs.Spec;
@@ -37,7 +38,7 @@ import java.util.regex.Pattern;
 
 /**
  * Directory walker supporting {@link Spec}s for includes and excludes.
- * The file system is traversed breadth first - all files in a directory will be
+ * The file system is traversed in depth-first prefix order - all files in a directory will be
  * visited before any child directory is visited.
  *
  * A file or directory will only be visited if it matches all includes and no
@@ -50,7 +51,7 @@ public class DirectoryFileTree implements MinimalFileTree, PatternFilterableFile
 
     private final File dir;
     private PatternSet patternSet;
-    private boolean depthFirst;
+    private boolean postfix;
 
     public DirectoryFileTree(File dir) {
         this(dir, new PatternSet());
@@ -110,13 +111,17 @@ public class DirectoryFileTree implements MinimalFileTree, PatternFilterableFile
      * the listener.  If it is a file, the file will be checked and notified.
      */
     public void visit(FileVisitor visitor) {
+        visitFrom(visitor, dir, new RelativePath(false));
+    }
+
+    public void visitFrom(FileVisitor visitor, File dir, RelativePath path) {
         AtomicBoolean stopFlag = new AtomicBoolean();
         Spec<FileTreeElement> spec = patternSet.getAsSpec();
         if (dir.exists()) {
             if (dir.isFile()) {
                 processSingleFile(dir, visitor, spec, stopFlag);
             } else {
-                walkDir(dir, new RelativePath(false), visitor, spec, stopFlag);
+                walkDir(dir, path, visitor, spec, stopFlag);
             }
         } else {
             LOGGER.info("file or directory '" + dir + "', not found");
@@ -125,7 +130,7 @@ public class DirectoryFileTree implements MinimalFileTree, PatternFilterableFile
 
     private void processSingleFile(File file, FileVisitor visitor, Spec<FileTreeElement> spec, AtomicBoolean stopFlag) {
         RelativePath path = new RelativePath(true, file.getName());
-        FileVisitDetailsImpl details = new FileVisitDetailsImpl(file, path, stopFlag);
+        FileVisitDetails details = new DefaultFileVisitDetails(file, path, stopFlag);
         if (isAllowed(details, spec)) {
             visitor.visitFile(details);
         }
@@ -140,12 +145,12 @@ public class DirectoryFileTree implements MinimalFileTree, PatternFilterableFile
             // else, might be a link which points to nothing, or has been removed while we're visiting, or ...
             throw new GradleException(String.format("Could not list contents of '%s'.", file));
         }
-        List<FileVisitDetailsImpl> dirs = new ArrayList<FileVisitDetailsImpl>();
+        List<FileVisitDetails> dirs = new ArrayList<FileVisitDetails>();
         for (int i = 0; !stopFlag.get() && i < children.length; i++) {
             File child = children[i];
             boolean isFile = child.isFile();
             RelativePath childPath = path.append(isFile, child.getName());
-            FileVisitDetailsImpl details = new FileVisitDetailsImpl(child, childPath, stopFlag);
+            FileVisitDetails details = new DefaultFileVisitDetails(child, childPath, stopFlag);
             if (isAllowed(details, spec)) {
                 if (isFile) {
                     visitor.visitFile(details);
@@ -157,8 +162,8 @@ public class DirectoryFileTree implements MinimalFileTree, PatternFilterableFile
 
         // now handle dirs
         for (int i = 0; !stopFlag.get() && i < dirs.size(); i++) {
-            FileVisitDetailsImpl dir = dirs.get(i);
-            if (depthFirst) {
+            FileVisitDetails dir = dirs.get(i);
+            if (postfix) {
                 walkDir(dir.getFile(), dir.getRelativePath(), visitor, spec, stopFlag);
                 visitor.visitDir(dir);
             } else {
@@ -172,21 +177,13 @@ public class DirectoryFileTree implements MinimalFileTree, PatternFilterableFile
         return spec.isSatisfiedBy(element);
     }
 
-    public DirectoryFileTree depthFirst() {
-        depthFirst = true;
+    /**
+     * Traverse directories (but not files) in postfix rather than prefix order.
+     *
+     * @return {@code this}
+     */
+    public DirectoryFileTree postfix() {
+        postfix = true;
         return this;
     }
-
-    private static class FileVisitDetailsImpl extends DefaultFileTreeElement implements FileVisitDetails {
-        private final AtomicBoolean stop;
-
-        private FileVisitDetailsImpl(File file, RelativePath relativePath, AtomicBoolean stop) {
-            super(file, relativePath);
-            this.stop = stop;
-        }
-
-        public void stopVisiting() {
-            stop.set(true);
-        }
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/MinimalFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/MinimalFileTree.java
index 6f0e38c..861a280 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/MinimalFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/MinimalFileTree.java
@@ -28,7 +28,7 @@ import org.gradle.api.file.FileVisitor;
  */
 public interface MinimalFileTree extends MinimalFileCollection {
     /**
-     * Visits the elements of this tree, in breadth-wise order.
+     * Visits the elements of this tree, in depth-first prefix order.
      */
     void visit(FileVisitor visitor);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/SingleIncludePatternFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/SingleIncludePatternFileTree.java
new file mode 100644
index 0000000..04daa6e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/SingleIncludePatternFileTree.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.collections;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.file.FileTreeElement;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.FileVisitor;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.internal.file.DefaultFileVisitDetails;
+import org.gradle.api.internal.file.pattern.PatternStep;
+import org.gradle.api.internal.file.pattern.RegExpPatternStep;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.api.tasks.util.PatternSet;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Directory walker that supports a single Ant-style include pattern
+ * and an optional exclude spec. Efficient in the sense that it will only
+ * exhaustively scan a directory hierarchy if, and from the point where,
+ * a '**' pattern is encountered.
+ */
+public class SingleIncludePatternFileTree implements MinimalFileTree {
+    private final File baseDir;
+    private final String includePattern;
+    private final List<String> patternSegments;
+    private final Spec<FileTreeElement> excludeSpec;
+
+    public SingleIncludePatternFileTree(File baseDir, String includePattern) {
+        this(baseDir, includePattern, Specs.<FileTreeElement>satisfyNone());
+    }
+
+    public SingleIncludePatternFileTree(File baseDir, String includePattern, Spec<FileTreeElement> excludeSpec) {
+        this.baseDir = baseDir;
+        if (includePattern.endsWith("/") || includePattern.endsWith("\\")) {
+            includePattern += "**";
+        }
+        this.includePattern = includePattern;
+        this.patternSegments = Arrays.asList(includePattern.split("[/\\\\]"));
+        this.excludeSpec = excludeSpec;
+    }
+
+    // TODO: important to visit files in prefix order? (contract says breadth-wise but probably means prefix.)
+    public void visit(FileVisitor visitor) {
+        doVisit(visitor, baseDir, new LinkedList<String>(), 0, new AtomicBoolean());
+    }
+
+    private void doVisit(FileVisitor visitor, File file, LinkedList<String> relativePath, int segmentIndex, AtomicBoolean stopFlag) {
+        if (stopFlag.get()) {
+            return;
+        }
+
+        String segment = patternSegments.get(segmentIndex);
+
+        if (segment.contains("**")) {
+            PatternSet patternSet = new PatternSet();
+            patternSet.include(includePattern);
+            patternSet.exclude(excludeSpec);
+            DirectoryFileTree fileTree = new DirectoryFileTree(baseDir, patternSet);
+            fileTree.visitFrom(visitor, file, new RelativePath(file.isFile(), relativePath.toArray(new String[relativePath.size()])));
+        } else if (segment.contains("*") || segment.contains("?")) {
+            PatternStep step = new RegExpPatternStep(segment, false);
+            File[] children = file.listFiles();
+            if (children == null) {
+                if (!file.canRead()) {
+                    throw new GradleException(String.format("Could not list contents of directory '%s' as it is not readable.", file));
+                }
+                // else, might be a link which points to nothing, or has been removed while we're visiting, or ...
+                throw new GradleException(String.format("Could not list contents of '%s'.", file));
+            }
+            for (File child : children) {
+                if (stopFlag.get()) { break; }
+                if (step.matches(child.getName(), child.isFile())) {
+                    relativePath.addLast(child.getName());
+                    doVisitDirOrFile(visitor, child, relativePath, segmentIndex + 1, stopFlag);
+                    relativePath.removeLast();
+                }
+            }
+        } else {
+            relativePath.addLast(segment);
+            doVisitDirOrFile(visitor, new File(file, segment), relativePath, segmentIndex + 1, stopFlag);
+            relativePath.removeLast();
+        }
+    }
+
+    private void doVisitDirOrFile(FileVisitor visitor, File file, LinkedList<String> relativePath, int segmentIndex, AtomicBoolean stopFlag) {
+        if (file.isFile()) {
+            if (segmentIndex == patternSegments.size()) {
+                RelativePath path = new RelativePath(true, relativePath.toArray(new String[relativePath.size()]));
+                FileVisitDetails details = new DefaultFileVisitDetails(file, path, stopFlag);
+                if (!excludeSpec.isSatisfiedBy(details)) {
+                    visitor.visitFile(details);
+                }
+            }
+        } else if (file.isDirectory()) {
+            RelativePath path = new RelativePath(false, relativePath.toArray(new String[relativePath.size()]));
+            FileVisitDetails details = new DefaultFileVisitDetails(file, path, stopFlag);
+            if (!excludeSpec.isSatisfiedBy(details)) {
+                visitor.visitDir(details);
+            }
+            if (segmentIndex < patternSegments.size()) {
+                doVisit(visitor, file, relativePath, segmentIndex, stopFlag);
+            }
+        }
+    }
+
+    public String getDisplayName() {
+        return "directory '" + baseDir + "' include '" + includePattern + "'";
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecImpl.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecImpl.java
index fabbe41..6d0d428 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecImpl.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecImpl.java
@@ -16,10 +16,10 @@
 package org.gradle.api.internal.file.copy;
 
 import groovy.lang.Closure;
-import org.codehaus.groovy.runtime.DefaultGroovyMethods;
 import org.gradle.api.Action;
 import org.gradle.api.file.*;
 import org.gradle.api.internal.ChainingTransformer;
+import org.gradle.api.internal.ClosureBackedAction;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.util.PatternSet;
@@ -369,7 +369,7 @@ public class CopySpecImpl implements CopySpec, ReadableCopySpec {
     }
 
     public CopySpec eachFile(Closure closure) {
-        actions.add((Action<? super FileCopyDetails>) DefaultGroovyMethods.asType(closure, Action.class));
+        actions.add(new ClosureBackedAction<FileCopyDetails>(closure));
         return this;
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitor.java
index 676ce9a..74783f3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitor.java
@@ -76,7 +76,7 @@ public class SyncCopySpecVisitor extends DelegatingCopySpecVisitor {
             }
         };
 
-        MinimalFileTree walker = new DirectoryFileTree(baseDestDir).depthFirst();
+        MinimalFileTree walker = new DirectoryFileTree(baseDestDir).postfix();
         walker.visit(visitor);
         visited.clear();
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/PathKeyFileStore.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/PathKeyFileStore.java
index 6b22744..e8f0be9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/PathKeyFileStore.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/PathKeyFileStore.java
@@ -21,10 +21,9 @@ import org.gradle.api.GradleException;
 import org.gradle.api.file.EmptyFileVisitor;
 import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.internal.file.IdentityFileResolver;
-import org.gradle.api.internal.file.collections.DirectoryFileTree;
+import org.gradle.api.internal.file.collections.MinimalFileTree;
+import org.gradle.api.internal.file.collections.SingleIncludePatternFileTree;
 import org.gradle.api.internal.file.copy.DeleteActionImpl;
-import org.gradle.api.tasks.util.PatternFilterable;
-import org.gradle.api.tasks.util.PatternSet;
 import org.gradle.util.GFileUtils;
 
 import java.io.File;
@@ -171,11 +170,8 @@ public class PathKeyFileStore implements FileStore<String>, FileStoreSearcher<St
         return getInProgressMarkerFile(file).exists();
     }
 
-    private DirectoryFileTree findFiles(String pattern) {
-        DirectoryFileTree fileTree = new DirectoryFileTree(baseDir);
-        PatternFilterable patternSet = new PatternSet();
-        patternSet.include(pattern);
-        return fileTree.filter(patternSet);
+    private MinimalFileTree findFiles(String pattern) {
+        return new SingleIncludePatternFileTree(baseDir, pattern);
     }
 
     protected FileStoreEntry entryAt(File file) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/CharSequenceNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/CharSequenceNotationParser.java
new file mode 100644
index 0000000..c8e74e2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/CharSequenceNotationParser.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.notations.parsers;
+
+public class CharSequenceNotationParser extends TypedNotationParser<CharSequence, String> {
+    public CharSequenceNotationParser() {
+        super(CharSequence.class);
+    }
+
+    @Override
+    protected String parseType(CharSequence notation) {
+        return notation.toString();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/AbstractProject.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/AbstractProject.java
index c5972e1..a4fbccd 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/AbstractProject.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/AbstractProject.java
@@ -24,6 +24,7 @@ import org.gradle.api.artifacts.Module;
 import org.gradle.api.artifacts.dsl.ArtifactHandler;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.component.SoftwareComponentContainer;
 import org.gradle.api.file.ConfigurableFileCollection;
 import org.gradle.api.file.ConfigurableFileTree;
 import org.gradle.api.file.CopySpec;
@@ -52,6 +53,7 @@ import org.gradle.configuration.ScriptPluginFactory;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.internal.Factory;
 import org.gradle.internal.reflect.Instantiator;
+import org.gradle.listener.ClosureBackedMethodInvocationDispatch;
 import org.gradle.listener.ListenerBroadcast;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.StandardOutputCapture;
@@ -140,6 +142,8 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
 
     private LoggingManagerInternal loggingManager;
 
+    private SoftwareComponentContainer softwareComponentContainer;
+
     private ExtensibleDynamicObject extensibleDynamicObject;
 
     private String description;
@@ -187,6 +191,7 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         scriptClassLoaderProvider = services.get(ScriptClassLoaderProvider.class);
         projectRegistry = services.get(IProjectRegistry.class);
         loggingManager = services.get(LoggingManagerInternal.class);
+        softwareComponentContainer = services.get(SoftwareComponentContainer.class);
 
         extensibleDynamicObject = new ExtensibleDynamicObject(this, services.get(Instantiator.class));
         if (parent != null) {
@@ -364,8 +369,6 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         this.configurationContainer = configurationContainer;
     }
 
-
-
     public Convention getConvention() {
         return extensibleDynamicObject.getConvention();
     }
@@ -465,6 +468,12 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         return this;
     }
 
+    public void ensureEvaluated() {
+        if (this.gradle.getStartParameter().isConfigureOnDemand()) {
+            this.evaluate();
+        }
+    }
+
     public TaskContainerInternal getTasks() {
         return taskContainer;
     }
@@ -764,11 +773,11 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
     }
 
     public void beforeEvaluate(Closure closure) {
-        evaluationListener.add("beforeEvaluate", closure);
+        evaluationListener.add(new ClosureBackedMethodInvocationDispatch("beforeEvaluate", closure));
     }
 
     public void afterEvaluate(Closure closure) {
-        evaluationListener.add("afterEvaluate", closure);
+        evaluationListener.add(new ClosureBackedMethodInvocationDispatch("afterEvaluate", closure));
     }
 
     public Logger getLogger() {
@@ -783,6 +792,10 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         return loggingManager;
     }
 
+    public SoftwareComponentContainer getComponents() {
+        return softwareComponentContainer;
+    }
+
     public Object property(String propertyName) throws MissingPropertyException {
         return extensibleDynamicObject.getProperty(propertyName);
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistry.java
index 2413616..70dc4bf 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistry.java
@@ -45,7 +45,8 @@ public class GradleInternalServiceRegistry extends DefaultServiceRegistry implem
 
     protected BuildExecuter createBuildExecuter() {
         return new DefaultBuildExecuter(
-                asList(new DefaultTasksBuildExecutionAction(),
+                asList(new OnlyWhenConfigureOnDemand(new ProjectEvaluatingAction(new TaskPathProjectEvaluator())),
+                        new DefaultTasksBuildExecutionAction(),
                         new ExcludedTaskFilteringBuildConfigurationAction(),
                         new TaskNameResolvingBuildConfigurationAction()),
                 asList(new DryRunBuildExecutionAction(),
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java
index 94bd8c2..381d7e1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java
@@ -39,6 +39,8 @@ public interface ProjectInternal extends Project, ProjectIdentifier, ScriptAware
 
     Project evaluate();
 
+    void ensureEvaluated();
+
     TaskContainerInternal getTasks();
 
     TaskContainerInternal getImplicitTasks();
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java
index 58590bb..2246e3e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java
@@ -21,17 +21,19 @@ import org.gradle.api.artifacts.Module;
 import org.gradle.api.artifacts.dsl.ArtifactHandler;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.component.SoftwareComponentContainer;
 import org.gradle.api.internal.ClassGenerator;
 import org.gradle.api.internal.ClassGeneratorBackedInstantiator;
 import org.gradle.api.internal.DependencyInjectingInstantiator;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
-import org.gradle.api.internal.artifacts.DefaultModule;
 import org.gradle.api.internal.artifacts.DependencyManagementServices;
 import org.gradle.api.internal.artifacts.DependencyResolutionServices;
+import org.gradle.api.internal.artifacts.ProjectBackedModule;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
+import org.gradle.api.internal.component.DefaultSoftwareComponentContainer;
 import org.gradle.api.internal.file.*;
 import org.gradle.api.internal.initialization.DefaultScriptHandlerFactory;
 import org.gradle.api.internal.initialization.ScriptClassLoaderProvider;
@@ -104,8 +106,8 @@ public class ProjectInternalServiceRegistry extends DefaultServiceRegistry imple
         return new DefaultTaskContainerFactory(get(Instantiator.class), get(ITaskFactory.class), project);
     }
 
-    protected Factory<ArtifactPublicationServices> createRepositoryHandlerFactory() {
-        return get(DependencyResolutionServices.class).getPublishServicesFactory();
+    protected ArtifactPublicationServices createArtifactPublicationServices() {
+        return get(DependencyResolutionServices.class).createArtifactPublicationServices();
     }
 
     protected RepositoryHandler createRepositoryHandler() {
@@ -116,6 +118,10 @@ public class ProjectInternalServiceRegistry extends DefaultServiceRegistry imple
         return get(DependencyResolutionServices.class).getConfigurationContainer();
     }
 
+    protected SoftwareComponentContainer createSoftwareComponentContainer() {
+        return new DefaultSoftwareComponentContainer(get(Instantiator.class));
+    }
+
     protected DependencyResolutionServices createDependencyResolutionServices() {
         return newDependencyResolutionServices();
     }
@@ -156,9 +162,8 @@ public class ProjectInternalServiceRegistry extends DefaultServiceRegistry imple
 
     protected DependencyMetaDataProvider createDependencyMetaDataProvider() {
         return new DependencyMetaDataProvider() {
-
             public Module getModule() {
-                return new DefaultModule(project.getGroup().toString(), project.getName(), project.getVersion().toString(), project.getStatus().toString());
+                return new ProjectBackedModule(project);
             }
         };
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistry.java
index b2ea432..95bbca0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistry.java
@@ -18,10 +18,11 @@ package org.gradle.api.internal.project;
 
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.TaskOutputsInternal;
-import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.api.internal.tasks.DefaultTaskInputs;
 import org.gradle.api.internal.tasks.DefaultTaskOutputs;
+import org.gradle.api.internal.tasks.TaskStatusNagger;
 import org.gradle.api.tasks.TaskInputs;
+import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.logging.LoggingManagerInternal;
 
@@ -39,11 +40,15 @@ public class TaskInternalServiceRegistry extends DefaultServiceRegistry implemen
     }
 
     protected TaskInputs createTaskInputs() {
-        return new DefaultTaskInputs(project.getFileResolver(), taskInternal);
+        return new DefaultTaskInputs(project.getFileResolver(), taskInternal, get(TaskStatusNagger.class));
     }
 
     protected TaskOutputsInternal createTaskOutputs() {
-        return new DefaultTaskOutputs(project.getFileResolver(), taskInternal);
+        return new DefaultTaskOutputs(project.getFileResolver(), taskInternal, get(TaskStatusNagger.class));
+    }
+
+    protected TaskStatusNagger createTaskStatusNagger() {
+        return new TaskStatusNagger(taskInternal);
     }
 
     protected LoggingManagerInternal createLoggingManager() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java
index bd63916..3f2725d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java
@@ -181,14 +181,8 @@ public class TopLevelBuildServiceRegistry extends DefaultServiceRegistry impleme
 
     protected InitScriptHandler createInitScriptHandler() {
         return new InitScriptHandler(
-                new CompositeInitScriptFinder(
-                        new ProvidedInitScriptFinder(),
-                        new UserHomeInitScriptFinder(),
-                        new DistributionInitScriptFinder(
-                                get(GradleDistributionLocator.class))),
-                new DefaultInitScriptProcessor(
-                        get(ScriptPluginFactory.class)));
-
+                new DefaultInitScriptProcessor(get(ScriptPluginFactory.class))
+        );
     }
 
     protected SettingsProcessor createSettingsProcessor() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputDirectoryPropertyAnnotationHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputDirectoryPropertyAnnotationHandler.java
index c6a81ce..b39b0c2 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputDirectoryPropertyAnnotationHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputDirectoryPropertyAnnotationHandler.java
@@ -16,7 +16,6 @@
 package org.gradle.api.internal.project.taskfactory;
 
 import org.gradle.api.Action;
-import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.Task;
 import org.gradle.api.Transformer;
 import org.gradle.internal.UncheckedException;
@@ -74,10 +73,7 @@ public class OutputDirectoryPropertyAnnotationHandler implements PropertyAnnotat
                         }
                         for (File file : files) {
                             file = GFileUtils.canonicalise(file);
-                            if (!file.isDirectory() && !file.mkdirs()) {
-                                throw new InvalidUserDataException(String.format(
-                                        "Cannot create directory '%s' specified for property '%s'.", file, context.getName()));
-                            }
+                            GFileUtils.mkdirs(file);
                         }
                     }
                 });
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputFilePropertyAnnotationHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputFilePropertyAnnotationHandler.java
index dd5874b..567c5fc 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputFilePropertyAnnotationHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputFilePropertyAnnotationHandler.java
@@ -16,7 +16,6 @@
 package org.gradle.api.internal.project.taskfactory;
 
 import org.gradle.api.Action;
-import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.Task;
 import org.gradle.api.Transformer;
 import org.gradle.internal.UncheckedException;
@@ -73,9 +72,7 @@ public class OutputFilePropertyAnnotationHandler implements PropertyAnnotationHa
                         }
                         for (File file : files) {
                             file = GFileUtils.canonicalise(file);
-                            if (!file.getParentFile().isDirectory() && !file.getParentFile().mkdirs()) {
-                                throw new InvalidUserDataException(String.format("Cannot create parent directory '%s' of file specified for property '%s'.", file.getParentFile(), context.getName()));
-                            }
+                            GFileUtils.mkdirs(file.getParentFile());
                         }
                     }
                 });
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/ResourceException.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/ResourceException.java
index 1ab913b..9ad8dc6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/ResourceException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/ResourceException.java
@@ -17,7 +17,9 @@
 package org.gradle.api.internal.resource;
 
 import org.gradle.api.GradleException;
+import org.gradle.api.internal.Contextual;
 
+ at Contextual
 public class ResourceException extends GradleException {
     public ResourceException(String message) {
         super(message);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java
index 7fb384e..4e61e86 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java
@@ -21,6 +21,8 @@ import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.Project;
 import org.gradle.api.Task;
 import org.gradle.api.UnknownTaskException;
+import org.gradle.api.internal.CachingDirectedGraphWalker;
+import org.gradle.api.internal.DirectedGraph;
 import org.gradle.api.internal.DynamicObject;
 import org.gradle.api.internal.NamedDomainObjectContainerConfigureDelegate;
 import org.gradle.api.internal.project.ProjectInternal;
@@ -30,6 +32,7 @@ import org.gradle.util.ConfigureUtil;
 import org.gradle.util.DeprecationLogger;
 import org.gradle.util.GUtil;
 
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -146,4 +149,12 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
     public DynamicObject getTasksAsDynamicObject() {
         return getElementsAsDynamicObject();
     }
+
+    public void actualize() {
+        new CachingDirectedGraphWalker<Task, Void>(new DirectedGraph<Task, Void>() {
+            public void getNodeValues(Task node, Collection<Void> values, Collection<Task> connectedNodes) {
+                connectedNodes.addAll(node.getTaskDependencies().getDependencies(node));
+            }
+        }).add(this).findValues();
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskInputs.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskInputs.java
index af87a1a..7bc7c8f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskInputs.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskInputs.java
@@ -32,10 +32,12 @@ public class DefaultTaskInputs implements TaskInputs {
     private final DefaultConfigurableFileCollection inputFiles;
     private final DefaultConfigurableFileCollection sourceFiles;
     private final FileResolver resolver;
+    private final TaskStatusNagger taskStatusNagger;
     private final Map<String, Object> properties = new HashMap<String, Object>();
 
-    public DefaultTaskInputs(FileResolver resolver, TaskInternal task) {
+    public DefaultTaskInputs(FileResolver resolver, TaskInternal task, TaskStatusNagger taskStatusNagger) {
         this.resolver = resolver;
+        this.taskStatusNagger = taskStatusNagger;
         inputFiles = new DefaultConfigurableFileCollection(String.format("%s input files", task), resolver, null);
         sourceFiles = new DefaultConfigurableFileCollection(String.format("%s source files", task), resolver, null);
     }
@@ -49,16 +51,19 @@ public class DefaultTaskInputs implements TaskInputs {
     }
 
     public TaskInputs files(Object... paths) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("TaskInputs.files(Object...)");
         inputFiles.from(paths);
         return this;
     }
 
     public TaskInputs file(Object path) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("TaskInputs.file(Object)");
         files(path);
         return this;
     }
 
     public TaskInputs dir(Object dirPath) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("TaskInputs.dir(Object)");
         inputFiles.from(resolver.resolveFilesAsTree(dirPath));
         return this;
     }
@@ -72,16 +77,19 @@ public class DefaultTaskInputs implements TaskInputs {
     }
 
     public TaskInputs source(Object... paths) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("TaskInputs.source(Object...)");
         sourceFiles.from(paths);
         return this;
     }
 
     public TaskInputs source(Object path) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("TaskInputs.source(Object)");
         sourceFiles.from(path);
         return this;
     }
 
     public TaskInputs sourceDir(Object path) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("TaskInputs.sourceDir(Object)");
         sourceFiles.from(resolver.resolveFilesAsTree(path));
         return this;
     }
@@ -117,11 +125,13 @@ public class DefaultTaskInputs implements TaskInputs {
     }
 
     public TaskInputs property(String name, Object value) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("TaskInputs.property(String, Object)");
         properties.put(name, value);
         return this;
     }
 
     public TaskInputs properties(Map<String, ?> properties) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("TaskInputs.properties(Map)");
         this.properties.putAll(properties);
         return this;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskOutputs.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskOutputs.java
index 59d9230..8d195af 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskOutputs.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskOutputs.java
@@ -32,8 +32,10 @@ public class DefaultTaskOutputs implements TaskOutputsInternal {
     private final DefaultConfigurableFileCollection outputFiles;
     private AndSpec<TaskInternal> upToDateSpec = new AndSpec<TaskInternal>();
     private TaskExecutionHistory history;
+    private final TaskStatusNagger taskStatusNagger;
 
-    public DefaultTaskOutputs(FileResolver resolver, TaskInternal task) {
+    public DefaultTaskOutputs(FileResolver resolver, TaskInternal task, TaskStatusNagger taskStatusNagger) {
+        this.taskStatusNagger = taskStatusNagger;
         outputFiles = new DefaultConfigurableFileCollection(String.format("%s output files", task), resolver, null);
         outputFiles.builtBy(task);
     }
@@ -43,10 +45,12 @@ public class DefaultTaskOutputs implements TaskOutputsInternal {
     }
 
     public void upToDateWhen(Closure upToDateClosure) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("TaskOutputs.upToDateWhen(Closure)");
         upToDateSpec = upToDateSpec.and(upToDateClosure);
     }
 
     public void upToDateWhen(Spec<? super Task> upToDateSpec) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("TaskOutputs.upToDateWhen(Spec)");
         this.upToDateSpec = this.upToDateSpec.and(upToDateSpec);
     }
 
@@ -59,17 +63,20 @@ public class DefaultTaskOutputs implements TaskOutputsInternal {
     }
 
     public TaskOutputs files(Object... paths) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("TaskOutputs.files(Object...)");
         outputFiles.from(paths);
         return this;
     }
 
     public TaskOutputs file(Object path) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("TaskOutputs.file(Object)");
         files(path);
         return this;
     }
 
-    public TaskOutputs dir(Object path) {
-        files(path);
+    public TaskOutputs dir(final Object path) {
+        taskStatusNagger.nagIfTaskNotInConfigurableState("TaskOutputs.dir(Object)");
+        outputFiles.from(path);
         return this;
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskContainerInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskContainerInternal.java
index 174a0c1..5a4e57c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskContainerInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskContainerInternal.java
@@ -15,9 +15,18 @@
  */
 package org.gradle.api.internal.tasks;
 
-import org.gradle.api.tasks.TaskContainer;
 import org.gradle.api.internal.DynamicObject;
+import org.gradle.api.tasks.TaskContainer;
 
 public interface TaskContainerInternal extends TaskContainer, TaskResolver {
     DynamicObject getTasksAsDynamicObject();
+
+    /**
+     * Force the entire graph to come into existence.
+     *
+     * Tasks may have dependencies that are abstract (e.g. a dependency on a task _name_). Calling this method
+     * will force all task dependencies to be actualised, which may mean new tasks are created because of things
+     * like task rules etc.
+     */
+    void actualize();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskStateInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskStateInternal.java
index 4033bff..c8ca938 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskStateInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskStateInternal.java
@@ -52,6 +52,9 @@ public class TaskStateInternal implements TaskState {
         this.executed = true;
     }
 
+    public boolean isConfigurable(){
+        return !executed && !executing;
+    }
     /**
      * Marks this task as executed with the given failure. This method can be called at most once.
      */
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskStatusNagger.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskStatusNagger.java
new file mode 100644
index 0000000..d9602f1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskStatusNagger.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks;
+
+import groovy.util.ObservableList;
+import org.gradle.api.Action;
+import org.gradle.api.Task;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.util.DeprecationLogger;
+
+import java.beans.PropertyChangeEvent;
+
+public class TaskStatusNagger {
+    private static final String DEPRECATION_MESSAGE = "Calling %s after task execution has started";
+    private static final String EXPLANAITION = "Check the configuration of %s";
+    private static final String EXPLANAITION_WITH_HINT = EXPLANAITION + ". You may have misused '<<' at task declaration";
+
+    private final TaskInternal taskInternal;
+    private boolean nagUser = true;
+    private boolean executingleftShiftAction;
+
+    public TaskStatusNagger(TaskInternal taskInternal) {
+        this.taskInternal = taskInternal;
+    }
+
+    public void nagIfTaskNotInConfigurableState(String method) {
+        if (!taskInternal.getStateInternal().isConfigurable() && nagUser) {
+            warn(method);
+        }
+    }
+
+    public void nagAboutMutatingListIfTaskNotInConfigurableState(String listname, PropertyChangeEvent evt) {
+        if (!taskInternal.getStateInternal().isConfigurable() && nagUser) {
+            if (evt instanceof ObservableList.ElementEvent) {
+                switch (((ObservableList.ElementEvent) evt).getChangeType()) {
+                    case ADDED:
+                        warn(String.format("%s.%s", listname, "add()"));
+                        break;
+                    case UPDATED:
+                        warn(String.format("%s.%s", listname, "set(int, Object)"));
+                        break;
+                    case REMOVED:
+                        warn(String.format("%s.%s", listname, "remove()"));
+                        break;
+                    case CLEARED:
+                        warn(String.format("%s.%s", listname, "clear()"));
+                        break;
+                    case MULTI_ADD:
+                        warn(String.format("%s.%s", listname, "addAll()"));
+                        break;
+                    case MULTI_REMOVE:
+                        warn(String.format("%s.%s", listname, "removeAll()"));
+                        break;
+                }
+            }
+        }
+    }
+
+    public Action<Task> leftShift(final Action<? super Task> action) {
+        return new Action<Task>() {
+            public void execute(Task task) {
+                executingleftShiftAction = true;
+                try {
+                    action.execute(task);
+                } finally {
+                    executingleftShiftAction = false;
+                }
+            }
+        };
+    }
+
+    private void warn(String method) {
+        if (executingleftShiftAction) {
+            DeprecationLogger.nagUserOfDeprecated(String.format(DEPRECATION_MESSAGE, method), String.format(EXPLANAITION_WITH_HINT, taskInternal));
+        } else {
+            DeprecationLogger.nagUserOfDeprecated(String.format(DEPRECATION_MESSAGE, method), String.format(EXPLANAITION, taskInternal));
+        }
+    }
+
+    public void whileDisabled(Runnable runnable) {
+        nagUser = false;
+        try {
+            runnable.run();
+        } finally {
+            nagUser = true;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuter.java
index 12322c3..d3b4dc5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuter.java
@@ -28,6 +28,9 @@ import org.gradle.api.tasks.StopActionException;
 import org.gradle.api.tasks.StopExecutionException;
 import org.gradle.api.tasks.TaskExecutionException;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * A {@link org.gradle.api.internal.tasks.TaskExecuter} which executes the actions of a task.
  */
@@ -53,7 +56,8 @@ public class ExecuteActionsTaskExecuter implements TaskExecuter {
 
     private GradleException executeActions(TaskInternal task, TaskStateInternal state) {
         logger.debug("Executing actions for {}.", task);
-        for (Action<? super Task> action : task.getActions()) {
+        final List<Action<? super Task>> actions = new ArrayList<Action<? super Task>>(task.getActions());
+        for (Action<? super Task> action : actions) {
             state.setDidWork(true);
             task.getStandardOutputCapture().start();
             try {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/SimpleXmlWriter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/SimpleXmlWriter.java
new file mode 100644
index 0000000..b56150b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/SimpleXmlWriter.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.xml;
+
+import org.gradle.internal.SystemProperties;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.LinkedList;
+
+/**
+ * <p>A streaming XML writer. Encodes characters and CDATA. Provides only basic state validation, and some simple indentation.</p>
+ *
+ * <p>This class also is-a Writer, and any characters written to this writer will be encoded as appropriate.</p>
+ *
+ * by Szczepan Faber, created at: 12/3/12
+ */
+public class SimpleXmlWriter extends Writer {
+    private enum Context {
+        Outside, Text, CData, StartTag, ElementContent
+    }
+
+    private final Writer output;
+    private final LinkedList<String> elements = new LinkedList<String>();
+    private Context context = Context.Outside;
+    private int squareBrackets;
+    private final String indent;
+
+    public SimpleXmlWriter(OutputStream output) throws IOException {
+        this(output, null);
+    }
+
+    public SimpleXmlWriter(OutputStream output, String indent) throws IOException {
+        this.indent = indent;
+        this.output = new OutputStreamWriter(output, "UTF-8");
+        writeXmlDeclaration("UTF-8", "1.1");
+    }
+
+    private void writeXmlDeclaration(String encoding, String ver) throws IOException {
+        writeRaw("<?xml version=\"");
+        writeRaw(ver);
+        writeRaw("\" encoding=\"");
+        writeRaw(encoding);
+        writeRaw("\"?>");
+    }
+
+    @Override
+    public void write(char[] chars, int offset, int length) throws IOException {
+        characters(chars, offset, length);
+    }
+
+    @Override
+    public void flush() throws IOException {
+        output.flush();
+    }
+
+    @Override
+    public void close() throws IOException {
+        // Does nothing
+    }
+
+    public SimpleXmlWriter characters(char[] characters) throws IOException {
+        characters(characters, 0, characters.length);
+        return this;
+    }
+
+    public SimpleXmlWriter characters(char[] characters, int start, int count) throws IOException {
+        if (context == Context.CData) {
+            writeCDATA(characters, start, count);
+        } else {
+            maybeStartText();
+            writeXmlEncoded(characters, start, count);
+        }
+        return this;
+    }
+
+    public SimpleXmlWriter characters(CharSequence characters) throws IOException {
+        if (context == Context.CData) {
+            writeCDATA(characters);
+        } else {
+            maybeStartText();
+            writeXmlEncoded(characters);
+        }
+        return this;
+    }
+
+    private void maybeStartText() throws IOException {
+        if (context == Context.Outside) {
+            throw new IllegalStateException("Cannot write text, as there are no started elements.");
+        }
+        if (context == Context.StartTag) {
+            writeRaw(">");
+        }
+        context = Context.Text;
+    }
+
+    private void maybeFinishStartTag() throws IOException {
+        if (context == Context.StartTag) {
+            writeRaw(">");
+            context = Context.ElementContent;
+        }
+    }
+
+    public SimpleXmlWriter startElement(String name) throws IOException {
+        if (!isValidXmlName(name)) {
+            throw new IllegalArgumentException(String.format("Invalid element name: '%s'", name));
+        }
+        if (context == Context.CData) {
+            throw new IllegalStateException("Cannot start element, as current CDATA node has not been closed.");
+        }
+        maybeFinishStartTag();
+        if (indent != null) {
+            writeRaw(SystemProperties.getLineSeparator());
+            for (int i = 0; i < elements.size(); i++) {
+                writeRaw(indent);
+            }
+        }
+        context = Context.StartTag;
+        elements.add(name);
+        writeRaw("<");
+        writeRaw(name);
+        return this;
+    }
+
+    public SimpleXmlWriter endElement() throws IOException {
+        if (context == Context.Outside) {
+            throw new IllegalStateException("Cannot end element, as there are no started elements.");
+        }
+        if (context == Context.CData) {
+            throw new IllegalStateException("Cannot end element, as current CDATA node has not been closed.");
+        }
+        if (context == Context.StartTag) {
+            writeRaw("/>");
+            elements.removeLast();
+        } else {
+            if (context != Context.Text && indent != null) {
+                writeRaw(SystemProperties.getLineSeparator());
+                for (int i = 1; i < elements.size(); i++) {
+                    writeRaw(indent);
+                }
+            }
+            writeRaw("</");
+            writeRaw(elements.removeLast());
+            writeRaw(">");
+        }
+        if (elements.isEmpty()) {
+            if (indent != null) {
+                writeRaw(SystemProperties.getLineSeparator());
+            }
+            output.flush();
+            context = Context.Outside;
+        } else {
+            context = Context.ElementContent;
+        }
+        return this;
+    }
+
+    private void writeCDATA(char[] cdata, int offset, int count) throws IOException {
+        int end = offset + count;
+        for (int i = offset; i < end; i++) {
+            writeCDATA(cdata[i]);
+        }
+    }
+
+    private void writeCDATA(CharSequence cdata) throws IOException {
+        int len = cdata.length();
+        for (int i = 0; i < len; i++) {
+            writeCDATA(cdata.charAt(i));
+        }
+    }
+
+    private void writeCDATA(char ch) throws IOException {
+        if (needsCDATAEscaping(ch)) {
+            writeRaw("]]><![CDATA[>");
+        } else if (!isLegalCharacter(ch)) {
+            writeRaw('?');
+        } else if (isRestrictedCharacter(ch)) {
+            writeRaw("]]>&#x");
+            writeRaw(Integer.toHexString(ch));
+            writeRaw(";<![CDATA[");
+        } else {
+            writeRaw(ch);
+        }
+    }
+
+    private boolean needsCDATAEscaping(char ch) {
+        switch (ch) {
+            case ']':
+                squareBrackets++;
+                return false;
+            case '>':
+                if (squareBrackets >= 2) {
+                    squareBrackets = 0;
+                    return true;
+                }
+                return false;
+            default:
+                squareBrackets = 0;
+                return false;
+        }
+    }
+
+    public SimpleXmlWriter startCDATA() throws IOException {
+        if (context == Context.CData) {
+            throw new IllegalStateException("Cannot start CDATA node, as current CDATA node has not been closed.");
+        }
+        maybeFinishStartTag();
+        writeRaw("<![CDATA[");
+        context = Context.CData;
+        squareBrackets = 0;
+        return this;
+    }
+
+    public SimpleXmlWriter endCDATA() throws IOException {
+        if (context != Context.CData) {
+            throw new IllegalStateException("Cannot end CDATA node, as not currently in a CDATA node.");
+        }
+        writeRaw("]]>");
+        context = Context.Text;
+        return this;
+    }
+
+    public SimpleXmlWriter attribute(String name, String value) throws IOException {
+        if (!isValidXmlName(name)) {
+            throw new IllegalArgumentException(String.format("Invalid attribute name: '%s'", name));
+        }
+        if (context != Context.StartTag) {
+            throw new IllegalStateException("Cannot write attribute [" + name + ":" + value + "]. You should write start element first.");
+        }
+
+        writeRaw(" ");
+        writeRaw(name);
+        writeRaw("=\"");
+        writeXmlAttributeEncoded(value);
+        writeRaw("\"");
+        return this;
+    }
+
+    private static boolean isValidXmlName(String name) {
+        int length = name.length();
+        if (length == 0) {
+            return false;
+        }
+        char ch = name.charAt(0);
+        if (!isValidNameStartChar(ch)) {
+            return false;
+        }
+        for (int i = 1; i < length; i++) {
+            ch = name.charAt(i);
+            if (!isValidNameChar(ch)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean isValidNameChar(char ch) {
+        if (isValidNameStartChar(ch)) {
+            return true;
+        }
+        if (ch >= '0' && ch <= '9') {
+            return true;
+        }
+        if (ch == '-' || ch == '.' || ch == '\u00b7') {
+            return true;
+        }
+        if (ch >= '\u0300' && ch <= '\u036f') {
+            return true;
+        }
+        if (ch >= '\u203f' && ch <= '\u2040') {
+            return true;
+        }
+        return false;
+    }
+
+    private static boolean isValidNameStartChar(char ch) {
+        if (ch >= 'A' && ch <= 'Z') {
+            return true;
+        }
+        if (ch >= 'a' && ch <= 'z') {
+            return true;
+        }
+        if (ch == ':' || ch == '_') {
+            return true;
+        }
+        if (ch >= '\u00c0' && ch <= '\u00d6') {
+            return true;
+        }
+        if (ch >= '\u00d8' && ch <= '\u00f6') {
+            return true;
+        }
+        if (ch >= '\u00f8' && ch <= '\u02ff') {
+            return true;
+        }
+        if (ch >= '\u0370' && ch <= '\u037d') {
+            return true;
+        }
+        if (ch >= '\u037f' && ch <= '\u1fff') {
+            return true;
+        }
+        if (ch >= '\u200c' && ch <= '\u200d') {
+            return true;
+        }
+        if (ch >= '\u2070' && ch <= '\u218f') {
+            return true;
+        }
+        if (ch >= '\u2c00' && ch <= '\u2fef') {
+            return true;
+        }
+        if (ch >= '\u3001' && ch <= '\ud7ff') {
+            return true;
+        }
+        if (ch >= '\uf900' && ch <= '\ufdcf') {
+            return true;
+        }
+        if (ch >= '\ufdf0' && ch <= '\ufffd') {
+            return true;
+        }
+        return false;
+    }
+
+    private void writeRaw(char c) throws IOException {
+        output.write(c);
+    }
+
+    private boolean isLegalCharacter(final char c) {
+        if (c == 0) {
+            return false;
+        } else if (c <= 0xD7FF) {
+            return true;
+        } else if (c < 0xE000) {
+            return false;
+        } else if (c <= 0xFFFD) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isRestrictedCharacter(char c) {
+        if (c == 0x9 || c == 0xA || c == 0xD || c == 0x85) {
+            return false;
+        } else if (c <= 0x1F) {
+            return true;
+        } else if (c < 0x7F) {
+            return false;
+        } else if (c <= 0x9F) {
+            return true;
+        }
+        return false;
+    }
+
+    private void writeRaw(String message) throws IOException {
+        output.write(message);
+    }
+
+    private void writeXmlEncoded(char[] message, int offset, int count) throws IOException {
+        int end = offset + count;
+        for (int i = offset; i < end; i++) {
+            writeXmlEncoded(message[i]);
+        }
+    }
+
+    private void writeXmlAttributeEncoded(CharSequence message) throws IOException {
+        assert message != null;
+        int len = message.length();
+        for (int i = 0; i < len; i++) {
+            writeXmlAttributeEncoded(message.charAt(i));
+        }
+    }
+
+    private void writeXmlAttributeEncoded(char ch) throws IOException {
+        if (ch == 9) {
+            writeRaw("	");
+        } else if (ch == 10) {
+            writeRaw("
");
+        } else if (ch == 13) {
+            writeRaw("
");
+        } else {
+            writeXmlEncoded(ch);
+        }
+    }
+
+    private void writeXmlEncoded(CharSequence message) throws IOException {
+        assert message != null;
+        int len = message.length();
+        for (int i = 0; i < len; i++) {
+            writeXmlEncoded(message.charAt(i));
+        }
+    }
+
+    private void writeXmlEncoded(char ch) throws IOException {
+        if (ch == '<') {
+            writeRaw("<");
+        } else if (ch == '>') {
+            writeRaw(">");
+        } else if (ch == '&') {
+            writeRaw("&");
+        } else if (ch == '"') {
+            writeRaw(""");
+        } else if (!isLegalCharacter(ch)) {
+            writeRaw('?');
+        } else if (isRestrictedCharacter(ch)) {
+            writeRaw("&#x");
+            writeRaw(Integer.toHexString(ch));
+            writeRaw(";");
+        } else {
+            writeRaw(ch);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/XmlTransformer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/XmlTransformer.java
new file mode 100644
index 0000000..ac92473
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/XmlTransformer.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.xml;
+
+import groovy.lang.Closure;
+import groovy.util.IndentPrinter;
+import groovy.util.Node;
+import groovy.util.XmlNodePrinter;
+import groovy.util.XmlParser;
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.Action;
+import org.gradle.api.Transformer;
+import org.gradle.api.XmlProvider;
+import org.gradle.api.internal.ClosureBackedAction;
+import org.gradle.api.internal.DomNode;
+import org.gradle.api.internal.IoActions;
+import org.gradle.internal.SystemProperties;
+import org.gradle.internal.UncheckedException;
+import org.gradle.util.GUtil;
+import org.gradle.util.TextUtil;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.InputSource;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+
+public class XmlTransformer implements Transformer<String, String> {
+    private final List<Action<? super XmlProvider>> actions = new ArrayList<Action<? super XmlProvider>>();
+    private String indentation = "  ";
+
+    public void addAction(Action<? super XmlProvider> provider) {
+        actions.add(provider);
+    }
+
+    public void setIndentation(String indentation) {
+        this.indentation = indentation;
+    }
+
+    public void addAction(Closure closure) {
+        actions.add(new ClosureBackedAction<XmlProvider>(closure));
+    }
+
+    public void transform(File destination, final String encoding, final Action<? super Writer> generator) {
+        IoActions.writeFile(destination, encoding, new Action<Writer>() {
+            public void execute(Writer writer) {
+                transform(writer, encoding, generator);
+            }
+        });
+    }
+
+    public void transform(File destination, final Action<? super Writer> generator) {
+        IoActions.writeFile(destination, new Action<Writer>() {
+            public void execute(Writer writer) {
+                transform(writer, generator);
+            }
+        });
+    }
+
+    public void transform(Writer destination, Action<? super Writer> generator) {
+        StringWriter stringWriter = new StringWriter();
+        generator.execute(stringWriter);
+        transform(stringWriter.toString(), destination);
+    }
+
+    public void transform(Writer destination, String encoding, Action<? super Writer> generator) {
+        StringWriter stringWriter = new StringWriter();
+        generator.execute(stringWriter);
+        transform(stringWriter.toString(), destination, encoding);
+    }
+
+    public String transform(String original) {
+        return doTransform(original).toString();
+    }
+
+    public void transform(String original, Writer destination) {
+        doTransform(original).writeTo(destination);
+    }
+
+    public void transform(String original, Writer destination, String encoding) {
+        doTransform(original).writeTo(destination, encoding);
+    }
+
+    public void transform(String original, OutputStream destination) {
+        doTransform(original).writeTo(destination);
+    }
+
+    public void transform(Node original, Writer destination) {
+        doTransform(original).writeTo(destination);
+    }
+
+    public void transform(Node original, OutputStream destination) {
+        doTransform(original).writeTo(destination);
+    }
+
+    public void transform(DomNode original, Writer destination) {
+        doTransform(original).writeTo(destination);
+    }
+
+    public void transform(DomNode original, OutputStream destination) {
+        doTransform(original).writeTo(destination);
+    }
+
+    private XmlProviderImpl doTransform(String original) {
+        return doTransform(new XmlProviderImpl(original));
+    }
+
+    private XmlProviderImpl doTransform(Node original) {
+        return doTransform(new XmlProviderImpl(original));
+    }
+
+    private XmlProviderImpl doTransform(DomNode original) {
+        return doTransform(new XmlProviderImpl(original));
+    }
+
+    private XmlProviderImpl doTransform(XmlProviderImpl provider) {
+        provider.apply(actions);
+        return provider;
+    }
+
+    private class XmlProviderImpl implements XmlProvider {
+        private StringBuilder builder;
+        private Node node;
+        private String stringValue;
+        private Element element;
+        private String publicId;
+        private String systemId;
+
+        public XmlProviderImpl(String original) {
+            this.stringValue = original;
+        }
+
+        public XmlProviderImpl(Node original) {
+            this.node = original;
+        }
+
+        public XmlProviderImpl(DomNode original) {
+            this.node = original;
+            publicId = original.getPublicId();
+            systemId = original.getSystemId();
+        }
+
+        public void apply(Iterable<Action<? super XmlProvider>> actions) {
+            for (Action<? super XmlProvider> action : actions) {
+                action.execute(this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringWriter writer = new StringWriter();
+            writeTo(writer);
+            return writer.toString();
+        }
+
+        public void writeTo(Writer writer) {
+            doWriteTo(writer, null);
+        }
+
+        public void writeTo(Writer writer, String encoding) {
+            doWriteTo(writer, encoding);
+        }
+
+        public void writeTo(OutputStream stream) {
+            try {
+                Writer writer = new OutputStreamWriter(stream, "UTF-8");
+                doWriteTo(writer, "UTF-8");
+                writer.flush();
+            } catch (IOException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+
+        public StringBuilder asString() {
+            if (builder == null) {
+                builder = new StringBuilder(toString());
+                node = null;
+                element = null;
+            }
+            return builder;
+        }
+
+        public Node asNode() {
+            if (node == null) {
+                try {
+                    node = new XmlParser().parseText(toString());
+                } catch (Exception e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+                builder = null;
+                element = null;
+            }
+            return node;
+        }
+
+        public Element asElement() {
+            if (element == null) {
+                Document document;
+                try {
+                    document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new StringReader(toString())));
+                } catch (Exception e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+                element = document.getDocumentElement();
+                builder = null;
+                node = null;
+            }
+            return element;
+        }
+
+        private void doWriteTo(Writer writer, String encoding) {
+            writeXmlDeclaration(writer, encoding);
+
+            try {
+                if (node != null) {
+                    printNode(node, writer);
+                } else if (element != null) {
+                    printDomNode(element, writer);
+                } else if (builder != null) {
+                    writer.append(TextUtil.toPlatformLineSeparators(stripXmlDeclaration(builder)));
+                } else {
+                    writer.append(TextUtil.toPlatformLineSeparators(stripXmlDeclaration(stringValue)));
+                }
+            } catch (IOException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+
+        private void printNode(Node node, Writer writer) {
+            final PrintWriter printWriter = new PrintWriter(writer);
+            if (GUtil.isTrue(publicId)) {
+                printWriter.format("<!DOCTYPE %s PUBLIC \"%s\" \"%s\">%n", node.name(), publicId, systemId);
+            }
+            IndentPrinter indentPrinter = new IndentPrinter(printWriter, indentation) {
+                @Override
+                public void println() {
+                    printWriter.println();
+                }
+            };
+            XmlNodePrinter nodePrinter = new XmlNodePrinter(indentPrinter);
+            nodePrinter.setPreserveWhitespace(true);
+            nodePrinter.print(node);
+            printWriter.flush();
+        }
+
+        private void printDomNode(org.w3c.dom.Node node, Writer destination) {
+            removeEmptyTextNodes(node); // empty text nodes hinder subsequent formatting
+            int indentAmount = determineIndentAmount();
+
+            try {
+                TransformerFactory factory = TransformerFactory.newInstance();
+                try {
+                    factory.setAttribute("indent-number", indentAmount);
+                } catch (IllegalArgumentException ignored) {
+                    /* unsupported by this transformer */
+                }
+
+                javax.xml.transform.Transformer transformer = factory.newTransformer();
+                transformer.setOutputProperty(OutputKeys.METHOD, "xml");
+                transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+                transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+                if (GUtil.isTrue(publicId)) {
+                    transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, publicId);
+                    transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, systemId);
+                }
+                try {
+                    // some impls support this but not factory.setAttribute("indent-number")
+                    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", String.valueOf(indentAmount));
+                } catch (IllegalArgumentException ignored) {
+                    /* unsupported by this transformer */
+                }
+
+                transformer.transform(new DOMSource(node), new StreamResult(destination));
+            } catch (TransformerException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+
+        private int determineIndentAmount() {
+            if (indentation.equals("\t")) { // not supported by javax.xml.transform.Transformer; use two spaces instead
+                return 2;
+            }
+            return indentation.length(); // assume indentation uses spaces
+        }
+
+        private void removeEmptyTextNodes(org.w3c.dom.Node node) {
+            org.w3c.dom.NodeList children = node.getChildNodes();
+
+            for (int i = 0; i < children.getLength(); i++) {
+                org.w3c.dom.Node child = children.item(i);
+                if (child.getNodeType() == org.w3c.dom.Node.TEXT_NODE && child.getNodeValue().trim().length() == 0) {
+                    node.removeChild(child);
+                } else {
+                    removeEmptyTextNodes(child);
+                }
+            }
+        }
+
+        private void writeXmlDeclaration(Writer writer, String encoding) {
+            try {
+                writer.write("<?xml version=\"1.0\"");
+                if (encoding != null) {
+                    writer.write(" encoding=\"");
+                    writer.write(encoding);
+                    writer.write("\"");
+                }
+                writer.write("?>");
+                writer.write(SystemProperties.getLineSeparator());
+            } catch (IOException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+        private boolean hasXmlDeclaration(String xml) {
+            return xml.startsWith("<?xml"); // XML declarations must be located at first position of first line
+        }
+
+        private String stripXmlDeclaration(CharSequence sequence) {
+            String str = sequence.toString();
+            if (hasXmlDeclaration(str)) {
+                str = str.substring(str.indexOf("?>") + 2);
+                str = StringUtils.stripStart(str, null);
+            }
+            return str;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/publish/ivy/internal/IvyNormalizedPublication.java b/subprojects/core/src/main/groovy/org/gradle/api/publish/ivy/internal/IvyNormalizedPublication.java
deleted file mode 100644
index 49af033..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/publish/ivy/internal/IvyNormalizedPublication.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy.internal;
-
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.internal.XmlTransformer;
-
-import java.io.File;
-import java.util.Set;
-
-public class IvyNormalizedPublication {
-
-    private final Module module;
-    private final File descriptorFile;
-    private final XmlTransformer descriptorTransformer;
-    private final Set<? extends Configuration> configurations;
-
-    public IvyNormalizedPublication(Module module, Set<? extends Configuration> configurations, File descriptorFile, XmlTransformer descriptorTransformer) {
-        this.module = module;
-        this.configurations = configurations;
-        this.descriptorFile = descriptorFile;
-        this.descriptorTransformer = descriptorTransformer;
-    }
-
-    public Module getModule() {
-        return module;
-    }
-
-    public Set<? extends Configuration> getConfigurations() {
-        return configurations;
-    }
-
-    public File getDescriptorFile() {
-        return descriptorFile;
-    }
-
-    public XmlTransformer getDescriptorTransformer() {
-        return descriptorTransformer;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/publish/ivy/internal/IvyPublisher.java b/subprojects/core/src/main/groovy/org/gradle/api/publish/ivy/internal/IvyPublisher.java
deleted file mode 100644
index 7810a97..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/publish/ivy/internal/IvyPublisher.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy.internal;
-
-import org.gradle.api.internal.artifacts.ArtifactPublisher;
-
-public class IvyPublisher {
-
-    private final ArtifactPublisher artifactPublisher;
-
-    public IvyPublisher(ArtifactPublisher artifactPublisher) {
-        this.artifactPublisher = artifactPublisher;
-    }
-
-    public void publish(IvyNormalizedPublication publication) {
-        artifactPublisher.publish(publication.getModule(), publication.getConfigurations(), publication.getDescriptorFile(), publication.getDescriptorTransformer());
-    }
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Directory.groovy b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Directory.groovy
index a3a6f4b..b965f19 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Directory.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Directory.groovy
@@ -19,6 +19,7 @@ package org.gradle.api.tasks
 import org.gradle.api.DefaultTask
 import org.gradle.api.InvalidUserDataException
 import org.gradle.util.DeprecationLogger
+import org.gradle.util.GFileUtils
 
 /**
  * Creates a directory.
@@ -36,12 +37,6 @@ public class Directory extends DefaultTask {
 
     @TaskAction
     protected void mkdir() {
-        if (dir.exists()) {
-            if (dir.isFile()) {
-                throw new InvalidUserDataException("The directory $name can't be created. There exists a file already with this path.")
-            }
-        } else {
-            dir.mkdirs()
-        }
+        GFileUtils.mkdirs(dir)
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskContainer.java
index 5f3659c..266042e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskContainer.java
@@ -81,7 +81,7 @@ public interface TaskContainer extends TaskCollection<Task>, NamedDomainObjectCo
      *
      * @param options The task creation options.
      * @return The newly created task object
-     * @throws InvalidUserDataException If a task with the given name already exsists in this project.
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
      */
     Task add(Map<String, ?> options) throws InvalidUserDataException;
 
@@ -96,7 +96,7 @@ public interface TaskContainer extends TaskCollection<Task>, NamedDomainObjectCo
      * @param options The task creation options.
      * @param configureClosure The closure to use to configure the task.
      * @return The newly created task object
-     * @throws InvalidUserDataException If a task with the given name already exsists in this project.
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
      */
     Task add(Map<String, ?> options, Closure configureClosure) throws InvalidUserDataException;
 
@@ -110,7 +110,7 @@ public interface TaskContainer extends TaskCollection<Task>, NamedDomainObjectCo
      * @param name The name of the task to be created
      * @param configureClosure The closure to use to configure the task.
      * @return The newly created task object
-     * @throws InvalidUserDataException If a task with the given name already exsists in this project.
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
      */
     Task add(String name, Closure configureClosure) throws InvalidUserDataException;
 
@@ -122,7 +122,7 @@ public interface TaskContainer extends TaskCollection<Task>, NamedDomainObjectCo
      *
      * @param name The name of the task to be created
      * @return The newly created task object
-     * @throws InvalidUserDataException If a task with the given name already exsists in this project.
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
      */
     Task add(String name) throws InvalidUserDataException;
 
@@ -135,7 +135,7 @@ public interface TaskContainer extends TaskCollection<Task>, NamedDomainObjectCo
      * @param name The name of the task to be created.
      * @param type The type of task to create.
      * @return The newly created task object
-     * @throws InvalidUserDataException If a task with the given name already exsists in this project.
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
      */
     <T extends Task> T add(String name, Class<T> type) throws InvalidUserDataException;
 
@@ -148,7 +148,7 @@ public interface TaskContainer extends TaskCollection<Task>, NamedDomainObjectCo
      *
      * @param name The name of the task to be created
      * @return The newly created task object
-     * @throws InvalidUserDataException If a task with the given name already exsists in this project.
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
      */
     Task replace(String name);
 
@@ -162,7 +162,7 @@ public interface TaskContainer extends TaskCollection<Task>, NamedDomainObjectCo
      * @param name The name of the task to be created.
      * @param type The type of task to create.
      * @return The newly created task object
-     * @throws InvalidUserDataException If a task with the given name already exsists in this project.
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
      */
     <T extends Task> T replace(String name, Class<T> type);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Upload.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Upload.java
index 7087bc3..7e12fad 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Upload.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Upload.java
@@ -17,55 +17,79 @@
 package org.gradle.api.tasks;
 
 import groovy.lang.Closure;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.Transformer;
 import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.PublishException;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.ConventionTask;
 import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
 import org.gradle.api.internal.artifacts.ArtifactPublisher;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.internal.artifacts.ivyservice.IvyModuleDescriptorWriter;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
+import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
 import org.gradle.util.ConfigureUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
+import javax.inject.Inject;
 import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+import static org.gradle.api.internal.Cast.cast;
+import static org.gradle.util.CollectionUtils.collect;
 
 /**
  * Uploads the artifacts of a {@link Configuration} to a set of repositories.
- * 
+ *
  * @author Hans Dockter
  */
 public class Upload extends ConventionTask {
-    private static Logger logger = LoggerFactory.getLogger(Upload.class);
 
     private Configuration configuration;
-
     private boolean uploadDescriptor;
-
     private File descriptorDestination;
-
-    /**
-     * The resolvers to delegate the uploads to. Usually a resolver corresponds to a repository.
-     */
     private RepositoryHandler repositories;
 
-    private ArtifactPublisher artifactPublisher;
+    private final ArtifactPublicationServices publicationServices;
 
-    public Upload() {
-        ArtifactPublicationServices publicationServices = getServices().getFactory(ArtifactPublicationServices.class).create();
-        repositories = publicationServices.getRepositoryHandler();
-        artifactPublisher = publicationServices.getArtifactPublisher();
+    @Inject
+    public Upload(ArtifactPublicationServices publicationServices) {
+        this.publicationServices = publicationServices;
+        repositories = publicationServices.createRepositoryHandler();
     }
 
     @TaskAction
     protected void upload() {
-        logger.info("Publishing configuration: " + configuration);
-        ConfigurationInternal configurationInternal = (ConfigurationInternal) configuration;
-        artifactPublisher.publish(
-                configurationInternal.getModule(), configuration.getHierarchy(),
-                isUploadDescriptor() ? getDescriptorDestination() : null,
-                null
-        );
+        getLogger().info("Publishing configuration: " + configuration);
+        Module module = ((ConfigurationInternal) configuration).getModule();
+        Set<Configuration> configurationsToPublish = configuration.getHierarchy();
+
+        ArtifactPublisher artifactPublisher = publicationServices.createArtifactPublisher();
+
+        try {
+            File descriptorDestination = isUploadDescriptor() ? getDescriptorDestination() : null;
+            if (descriptorDestination != null) {
+                Set<Configuration> allConfigurations = configurationsToPublish.iterator().next().getAll();
+                ModuleDescriptorConverter moduleDescriptorConverter = publicationServices.getDescriptorFileModuleConverter();
+                ModuleDescriptor moduleDescriptor = moduleDescriptorConverter.convert(allConfigurations, module);
+                IvyModuleDescriptorWriter ivyModuleDescriptorWriter = publicationServices.getIvyModuleDescriptorWriter();
+                ivyModuleDescriptorWriter.write(moduleDescriptor, descriptorDestination);
+            }
+
+            List<DependencyResolver> resolvers = collect(repositories, new Transformer<DependencyResolver, ArtifactRepository>() {
+                public DependencyResolver transform(ArtifactRepository repository) {
+                    return cast(ArtifactRepositoryInternal.class, repository).createResolver();
+                }
+            });
+            artifactPublisher.publish(resolvers,  module, configurationsToPublish, descriptorDestination);
+        } catch (Exception e) {
+            throw new PublishException(String.format("Could not publish configuration '%s'", configuration.getName()), e);
+        }
     }
 
     /**
@@ -86,6 +110,7 @@ public class Upload extends ConventionTask {
         return descriptorDestination;
     }
 
+    @SuppressWarnings("UnusedDeclaration")
     public void setDescriptorDestination(File descriptorDestination) {
         this.descriptorDestination = descriptorDestination;
     }
@@ -117,7 +142,7 @@ public class Upload extends ConventionTask {
 
     /**
      * Returns the artifacts which will be uploaded.
-     * 
+     *
      * @return the artifacts.
      */
     @InputFiles
@@ -126,12 +151,4 @@ public class Upload extends ConventionTask {
         return configuration == null ? null : configuration.getAllArtifacts().getFiles();
     }
 
-    void setRepositories(RepositoryHandler repositories) {
-        this.repositories = repositories;
-    }
-
-    void setArtifactPublisher(ArtifactPublisher artifactPublisher) {
-        this.artifactPublisher = artifactPublisher;
-    }
-
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/util/PatternSet.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/util/PatternSet.java
index 1c1fcfa..5379eff 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/util/PatternSet.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/util/PatternSet.java
@@ -16,6 +16,8 @@
 
 package org.gradle.api.tasks.util;
 
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 import groovy.lang.Closure;
 import org.apache.tools.ant.DirectoryScanner;
 import org.gradle.api.Action;
@@ -23,6 +25,7 @@ import org.gradle.api.file.FileTreeElement;
 import org.gradle.api.file.RelativePath;
 import org.gradle.api.internal.file.RelativePathSpec;
 import org.gradle.api.internal.file.pattern.PatternMatcherFactory;
+import org.gradle.api.internal.notations.parsers.CharSequenceNotationParser;
 import org.gradle.api.specs.*;
 import org.gradle.api.tasks.AntBuilderAware;
 import org.gradle.api.tasks.util.internal.PatternSetAntBuilderDelegate;
@@ -31,14 +34,14 @@ import org.gradle.util.CollectionUtils;
 import java.util.*;
 
 /**
- * {@inheritDoc}
+ * Standalone implementation of {@link PatternFilterable}.
  */
 public class PatternSet implements AntBuilderAware, PatternFilterable {
 
-    private Set<String> includes = new LinkedHashSet<String>();
-    private Set<String> excludes = new LinkedHashSet<String>();
-    private Set<Spec<FileTreeElement>> includeSpecs = new LinkedHashSet<Spec<FileTreeElement>>();
-    private Set<Spec<FileTreeElement>> excludeSpecs = new LinkedHashSet<Spec<FileTreeElement>>();
+    private final Set<String> includes = Sets.newLinkedHashSet();
+    private final Set<String> excludes = Sets.newLinkedHashSet();
+    private final Set<Spec<FileTreeElement>> includeSpecs = Sets.newLinkedHashSet();
+    private final Set<Spec<FileTreeElement>> excludeSpecs = Sets.newLinkedHashSet();
 
     boolean caseSensitive = true;
 
@@ -113,43 +116,32 @@ public class PatternSet implements AntBuilderAware, PatternFilterable {
     }
 
     public Spec<FileTreeElement> getAsSpec() {
-        Spec<FileTreeElement> includeSpec = Specs.satisfyAll();
-
-        Set<String> includes = this.getIncludes();
-        Set<Spec<FileTreeElement>> includeSpecs = getIncludeSpecs();
-
-        boolean hasIncludes = !includes.isEmpty() || !includeSpecs.isEmpty();
-        if (hasIncludes) {
-            List<Spec<FileTreeElement>> matchers = new LinkedList<Spec<FileTreeElement>>();
-            for (String include : includes) {
-                Spec<RelativePath> patternMatcher = PatternMatcherFactory.getPatternMatcher(true, isCaseSensitive(), include);
-                matchers.add(new RelativePathSpec(patternMatcher));
-            }
-            matchers.addAll(includeSpecs);
-            includeSpec = new OrSpec<FileTreeElement>(matchers);
+        return new AndSpec<FileTreeElement>(getAsIncludeSpec(), new NotSpec<FileTreeElement>(getAsExcludeSpec()));
+    }
+
+    public Spec<FileTreeElement> getAsIncludeSpec() {
+        List<Spec<FileTreeElement>> matchers = Lists.newArrayList();
+        for (String include : includes) {
+            Spec<RelativePath> patternMatcher = PatternMatcherFactory.getPatternMatcher(true, caseSensitive, include);
+            matchers.add(new RelativePathSpec(patternMatcher));
         }
 
+        matchers.addAll(includeSpecs);
+        return new OrSpec<FileTreeElement>(matchers);
+    }
 
-        Set<String> excludes = getExcludes();
-        Collection<String> allExcludes = new HashSet<String>(excludes);
+    public Spec<FileTreeElement> getAsExcludeSpec() {
+        Collection<String> allExcludes = Sets.newLinkedHashSet(excludes);
         Collections.addAll(allExcludes, DirectoryScanner.getDefaultExcludes());
 
-        Set<Spec<FileTreeElement>> excludeSpecs = getExcludeSpecs();
-
-        List<Spec<FileTreeElement>> matchers = new LinkedList<Spec<FileTreeElement>>();
+        List<Spec<FileTreeElement>> matchers = Lists.newArrayList();
         for (String exclude : allExcludes) {
-            Spec<RelativePath> patternMatcher = PatternMatcherFactory.getPatternMatcher(false, isCaseSensitive(), exclude);
+            Spec<RelativePath> patternMatcher = PatternMatcherFactory.getPatternMatcher(false, caseSensitive, exclude);
             matchers.add(new RelativePathSpec(patternMatcher));
         }
 
         matchers.addAll(excludeSpecs);
-        Spec<FileTreeElement> excludeSpec = new NotSpec<FileTreeElement>(new OrSpec<FileTreeElement>(matchers));
-
-        if (!hasIncludes) {
-            return excludeSpec;
-        }
-
-        return new AndSpec<FileTreeElement>(includeSpec, excludeSpec);
+        return new OrSpec<FileTreeElement>(matchers);
     }
 
     public Set<String> getIncludes() {
@@ -165,11 +157,15 @@ public class PatternSet implements AntBuilderAware, PatternFilterable {
         return include(includes);
     }
     public PatternSet include(String... includes) {
-        return include(Arrays.asList(includes));
+        Collections.addAll(this.includes, includes);
+        return this;
     }
 
-    public PatternSet include(Iterable<String> includes) {
-        CollectionUtils.addAll(this.includes, includes);
+    public PatternSet include(Iterable includes) {
+        CharSequenceNotationParser parser = new CharSequenceNotationParser();
+        for (Object include : includes) {
+            this.includes.add(parser.parseNotation(include));
+        }
         return this;
     }
 
@@ -201,9 +197,8 @@ public class PatternSet implements AntBuilderAware, PatternFilterable {
     }
 
     /*
-    This can't be called just include, because it has the same erasure as include(Iterable<String>)
+    This can't be called just include, because it has the same erasure as include(Iterable<String>).
      */
-
     public PatternSet includeSpecs(Iterable<Spec<FileTreeElement>> includeSpecs) {
         CollectionUtils.addAll(this.includeSpecs, includeSpecs);
         return this;
@@ -219,8 +214,11 @@ public class PatternSet implements AntBuilderAware, PatternFilterable {
         return this;
     }
 
-    public PatternSet exclude(Iterable<String> excludes) {
-        CollectionUtils.addAll(this.excludes, excludes);
+    public PatternSet exclude(Iterable excludes) {
+        CharSequenceNotationParser parser = new CharSequenceNotationParser();
+        for (Object exclude : excludes) {
+            this.excludes.add(parser.parseNotation(exclude));
+        }
         return this;
     }
 
@@ -241,10 +239,10 @@ public class PatternSet implements AntBuilderAware, PatternFilterable {
 
     public Object addToAntBuilder(Object node, String childNodeName) {
 
-        if (!getIncludeSpecs().isEmpty() || !getExcludeSpecs().isEmpty()) {
+        if (!includeSpecs.isEmpty() || !excludeSpecs.isEmpty()) {
             throw new UnsupportedOperationException("Cannot add include/exclude specs to Ant node. Only include/exclude patterns are currently supported.");
         }
 
-        return new PatternSetAntBuilderDelegate(getIncludes(), getExcludes(), isCaseSensitive()).addToAntBuilder(node, childNodeName);
+        return new PatternSetAntBuilderDelegate(includes, excludes, caseSensitive).addToAntBuilder(node, childNodeName);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/DefaultSerializer.java b/subprojects/core/src/main/groovy/org/gradle/cache/DefaultSerializer.java
deleted file mode 100644
index 1b71ee5..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/DefaultSerializer.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.cache;
-
-import org.gradle.internal.io.ClassLoaderObjectInputStream;
-import org.gradle.messaging.serialize.Serializer;
-
-import java.io.*;
-
-public class DefaultSerializer<T> implements Serializer<T> {
-    private ClassLoader classLoader;
-
-    public DefaultSerializer() {
-        classLoader = getClass().getClassLoader();
-    }
-
-    public DefaultSerializer(ClassLoader classLoader) {
-        this.classLoader = classLoader != null ? classLoader : getClass().getClassLoader();
-    }
-
-    public ClassLoader getClassLoader() {
-        return classLoader;
-    }
-
-    public void setClassLoader(ClassLoader classLoader) {
-        this.classLoader = classLoader;
-    }
-
-    public T read(InputStream instr) throws Exception {
-        try {
-            return (T) new ClassLoaderObjectInputStream(instr, classLoader).readObject();
-        } catch (StreamCorruptedException e) {
-            return null;
-        }
-    }
-
-    public void write(OutputStream outstr, T value) throws Exception {
-        ObjectOutputStream objectStr = new ObjectOutputStream(outstr);
-        objectStr.writeObject(value);
-        objectStr.flush();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/PersistentCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/PersistentCache.java
index 8f81c58..88cc810 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/PersistentCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/PersistentCache.java
@@ -57,4 +57,14 @@ public interface PersistentCache extends CacheAccess {
      * <p>The returned cache may not be used by an action being run from {@link #longRunningOperation(String, org.gradle.internal.Factory)}.
      */
     <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Serializer<V> valueSerializer);
+
+    /**
+     * Creates an indexed cache implementation that is contained within this cache. This method may be used at any time.
+     *
+     * <p>The returned cache may only be used by an action being run from {@link #useCache(String, org.gradle.internal.Factory)}.
+     * In this instance, an exclusive lock will be held on the cache.
+     *
+     * <p>The returned cache may not be used by an action being run from {@link #longRunningOperation(String, org.gradle.internal.Factory)}.
+     */
+    <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheAccess.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheAccess.java
index 1acd5f3..9ce31a2 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheAccess.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheAccess.java
@@ -17,7 +17,7 @@ package org.gradle.cache.internal;
 
 import net.jcip.annotations.ThreadSafe;
 import org.gradle.cache.CacheAccess;
-import org.gradle.cache.DefaultSerializer;
+import org.gradle.messaging.serialize.DefaultSerializer;
 import org.gradle.cache.PersistentIndexedCache;
 import org.gradle.cache.internal.btree.BTreePersistentIndexedCache;
 import org.gradle.internal.Factories;
@@ -237,9 +237,13 @@ public class DefaultCacheAccess implements CacheAccess {
     }
 
     public <K, V> PersistentIndexedCache<K, V> newCache(final File cacheFile, final Class<K> keyType, final Serializer<V> valueSerializer) {
+        return newCache(cacheFile, new DefaultSerializer<K>(keyType.getClassLoader()), valueSerializer);
+    }
+
+    public <K, V> PersistentIndexedCache<K, V> newCache(final File cacheFile, final Serializer<K> keySerializer, final Serializer<V> valueSerializer) {
         Factory<BTreePersistentIndexedCache<K, V>> indexedCacheFactory = new Factory<BTreePersistentIndexedCache<K, V>>() {
             public BTreePersistentIndexedCache<K, V> create() {
-                return doCreateCache(cacheFile, new DefaultSerializer<K>(keyType.getClassLoader()), valueSerializer);
+                return doCreateCache(cacheFile, keySerializer, valueSerializer);
             }
         };
         MultiProcessSafePersistentIndexedCache<K, V> indexedCache = new MultiProcessSafePersistentIndexedCache<K, V>(indexedCacheFactory, fileAccess);
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheFactory.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheFactory.java
index bb4d303..e668d55 100755
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheFactory.java
@@ -20,6 +20,7 @@ import org.gradle.api.Action;
 import org.gradle.internal.Factory;
 import org.gradle.cache.*;
 import org.gradle.cache.internal.btree.BTreePersistentIndexedCache;
+import org.gradle.messaging.serialize.DefaultSerializer;
 import org.gradle.messaging.serialize.Serializer;
 import org.gradle.util.GFileUtils;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheRepository.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheRepository.java
index 9790352..545d862 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheRepository.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheRepository.java
@@ -19,6 +19,7 @@ import org.gradle.CacheUsage;
 import org.gradle.api.Action;
 import org.gradle.api.invocation.Gradle;
 import org.gradle.cache.*;
+import org.gradle.messaging.serialize.DefaultSerializer;
 import org.gradle.messaging.serialize.Serializer;
 import org.gradle.util.GradleVersion;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultFileLockManager.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultFileLockManager.java
index 4c4097b..a328e8a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultFileLockManager.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultFileLockManager.java
@@ -21,7 +21,10 @@ import org.gradle.util.GFileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.*;
+import java.io.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArraySet;
 
@@ -99,7 +102,7 @@ public class DefaultFileLockManager implements FileLockManager {
                 lockFile = new File(target.getParentFile(), target.getName() + ".lock");
             }
 
-            lockFile.getParentFile().mkdirs();
+            GFileUtils.mkdirs(lockFile.getParentFile());
             lockFile.createNewFile();
             lockFileAccess = new RandomAccessFile(lockFile, "rw");
             try {
@@ -237,7 +240,7 @@ public class DefaultFileLockManager implements FileLockManager {
                     LOGGER.debug("Could not lock information region for {}. Ignoring.", displayName);
                 } else {
                     try {
-                        if (lockFileAccess.length() < INFORMATION_REGION_POS) {
+                        if (lockFileAccess.length() <= INFORMATION_REGION_POS) {
                             LOGGER.debug("Lock file for {} is too short to contain information region. Ignoring.", displayName);
                         } else {
                             lockFileAccess.seek(INFORMATION_REGION_POS);
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStore.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStore.java
index 4f0347a..74d7736 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStore.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStore.java
@@ -39,7 +39,7 @@ public class DefaultPersistentDirectoryStore implements ReferencablePersistentCa
     }
 
     public DefaultPersistentDirectoryStore open() {
-        GFileUtils.createDirectory(dir);
+        GFileUtils.mkdirs(dir);
         cacheAccess = createCacheAccess();
         try {
             cacheAccess.open(lockMode);
@@ -122,6 +122,10 @@ public class DefaultPersistentDirectoryStore implements ReferencablePersistentCa
         return cacheAccess.newCache(cacheFile, keyType, valueSerializer);
     }
 
+    public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
+        return cacheAccess.newCache(cacheFile, keySerializer, valueSerializer);
+    }
+
     public <T> T useCache(String operationDisplayName, Factory<? extends T> action) {
         return cacheAccess.useCache(operationDisplayName, action);
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCache.java
index 271ce4a..72a523f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCache.java
@@ -104,6 +104,10 @@ public class DelegateOnDemandPersistentDirectoryCache implements ReferencablePer
         throw new UnsupportedOperationException();
     }
 
+    public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
+        throw new UnsupportedOperationException();
+    }
+
     public String toString(){
         return String.format("On Demand Cache for %s", delegateCache.toString());
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultBuildConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultBuildConfigurer.java
index 51f2aff..454c69b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultBuildConfigurer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultBuildConfigurer.java
@@ -20,15 +20,24 @@ import org.gradle.api.Project;
 import org.gradle.api.internal.Actions;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
 
 public class DefaultBuildConfigurer implements BuildConfigurer {
     private Action<Project> actions;
+    private final static Logger LOG = Logging.getLogger(DefaultBuildConfigurer.class);
 
     public DefaultBuildConfigurer(Action<? super ProjectInternal>... actions) {
         this.actions = Actions.castBefore(ProjectInternal.class, Actions.composite(actions));
     }
 
     public void configure(GradleInternal gradle) {
-        gradle.getRootProject().allprojects(actions);
+        if (gradle.getStartParameter().isConfigureOnDemand()) {
+            LOG.lifecycle("Thanks for using the incubating configuration-on-demand mode. Enjoy it and let us know how it works for you.");
+            gradle.addProjectEvaluationListener(new ImplicitTasksConfigurer());
+            gradle.getRootProject().evaluate();
+        } else {
+            gradle.getRootProject().allprojects(actions);
+        }
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/ImplicitTasksConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/configuration/ImplicitTasksConfigurer.java
index 1d1d3b2..34586da 100644
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/ImplicitTasksConfigurer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/ImplicitTasksConfigurer.java
@@ -16,9 +16,12 @@
 package org.gradle.configuration;
 
 import org.gradle.api.Action;
-import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.Project;
+import org.gradle.api.ProjectEvaluationListener;
+import org.gradle.api.ProjectState;
 
-public class ImplicitTasksConfigurer implements Action<ProjectInternal> {
+//This one should go away once we complete the auto-apply plugins
+public class ImplicitTasksConfigurer implements Action<Project>, ProjectEvaluationListener {
     public static final String HELP_GROUP = "help";
     public static final String HELP_TASK = "help";
     public static final String PROJECTS_TASK = "projects";
@@ -27,7 +30,13 @@ public class ImplicitTasksConfigurer implements Action<ProjectInternal> {
     public static final String DEPENDENCIES_TASK = "dependencies";
     public static final String DEPENDENCY_INSIGHT_TASK = "dependencyInsight";
 
-    public void execute(ProjectInternal project) {
+    public void beforeEvaluate(Project project) {}
+
+    public void afterEvaluate(Project project, ProjectState state) {
+        execute(project);
+    }
+
+    public void execute(Project project) {
         project.getPlugins().apply("help-tasks");
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/OnlyWhenConfigureOnDemand.java b/subprojects/core/src/main/groovy/org/gradle/execution/OnlyWhenConfigureOnDemand.java
new file mode 100644
index 0000000..d6b7e16
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/OnlyWhenConfigureOnDemand.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.execution;
+
+/**
+ * Action that only executes the delegate when configure on demand mode is 'on'
+ *
+ * by Szczepan Faber, created at: 1/8/13
+ */
+public class OnlyWhenConfigureOnDemand implements BuildConfigurationAction {
+
+    private final BuildConfigurationAction delegate;
+
+    public OnlyWhenConfigureOnDemand(BuildConfigurationAction delegate) {
+        this.delegate = delegate;
+    }
+
+    public void configure(BuildExecutionContext context) {
+        if (context.getGradle().getStartParameter().isConfigureOnDemand()) {
+            delegate.configure(context);
+        } else {
+            context.proceed();
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/ProjectEvaluatingAction.java b/subprojects/core/src/main/groovy/org/gradle/execution/ProjectEvaluatingAction.java
new file mode 100644
index 0000000..8fdde16
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/ProjectEvaluatingAction.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.execution;
+
+import org.gradle.api.internal.project.ProjectInternal;
+
+import java.util.List;
+
+/**
+ * Ensures that projects resolved from the command line task names are evaluated.
+ *
+ * by Szczepan Faber, created at: 11/22/12
+ */
+public class ProjectEvaluatingAction implements BuildConfigurationAction {
+
+    private final TaskPathProjectEvaluator evaluator;
+
+    public ProjectEvaluatingAction(TaskPathProjectEvaluator evaluator) {
+        this.evaluator = evaluator;
+    }
+
+    public void configure(BuildExecutionContext context) {
+        List<String> taskNames = context.getGradle().getStartParameter().getTaskNames();
+        ProjectInternal project = context.getGradle().getDefaultProject();
+
+        project.evaluate();
+
+        for (String path : taskNames) {
+            evaluator.evaluateByPath(project, path);
+        }
+        context.proceed();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/ProjectFinderByTaskPath.java b/subprojects/core/src/main/groovy/org/gradle/execution/ProjectFinderByTaskPath.java
new file mode 100644
index 0000000..8e9201d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/ProjectFinderByTaskPath.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.execution;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Project;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.util.NameMatcher;
+
+import java.util.Map;
+
+/**
+ * by Szczepan Faber, created at: 1/3/13
+ */
+public class ProjectFinderByTaskPath {
+
+    public ProjectInternal findProject(String taskPath, ProjectInternal startFrom) {
+        if (!taskPath.contains(Project.PATH_SEPARATOR)) {
+            throw new IllegalArgumentException("I can only find tasks based on a task path (e.g. containing ':'). However, '" + taskPath + "' was passed.");
+        }
+        String projectPath = StringUtils.substringBeforeLast(taskPath, Project.PATH_SEPARATOR);
+        projectPath = projectPath.length() == 0 ? Project.PATH_SEPARATOR : projectPath;
+        return findProjectNow(projectPath, startFrom);
+    }
+
+    private static ProjectInternal findProjectNow(String path, ProjectInternal startFrom) {
+        if (path.equals(Project.PATH_SEPARATOR)) {
+            return startFrom.getRootProject();
+        }
+        Project current = startFrom;
+        if (path.startsWith(Project.PATH_SEPARATOR)) {
+            current = current.getRootProject();
+            path = path.substring(1);
+        }
+        for (String pattern : path.split(Project.PATH_SEPARATOR)) {
+            Map<String, Project> children = current.getChildProjects();
+
+            NameMatcher matcher = new NameMatcher();
+            Project child = matcher.find(pattern, children);
+            if (child != null) {
+                current = child;
+                continue;
+            }
+
+            throw new ProjectLookupException(matcher.formatErrorMessage("project", current));
+        }
+
+        return (ProjectInternal) current;
+    }
+
+    public static class ProjectLookupException extends InvalidUserDataException {
+        public ProjectLookupException(String message) {
+            super(message);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/TaskPathProjectEvaluator.java b/subprojects/core/src/main/groovy/org/gradle/execution/TaskPathProjectEvaluator.java
new file mode 100644
index 0000000..e4808ad
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/TaskPathProjectEvaluator.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.execution;
+
+import org.gradle.api.Project;
+import org.gradle.api.internal.project.ProjectInternal;
+
+/**
+ * by Szczepan Faber, created at: 1/8/13
+ */
+public class TaskPathProjectEvaluator {
+
+    private final ProjectFinderByTaskPath finder;
+
+    public TaskPathProjectEvaluator() {
+        this(new ProjectFinderByTaskPath());
+    }
+
+    TaskPathProjectEvaluator(ProjectFinderByTaskPath finder) {
+        this.finder = finder;
+    }
+
+    public void evaluateByPath(ProjectInternal project, String path) {
+        if (path.contains(Project.PATH_SEPARATOR)) {
+            ProjectInternal foundProject = finder.findProject(path, project);
+            foundProject.evaluate();
+        } else {
+            project.evaluate();
+            for (Project sub : project.getSubprojects()) {
+                ((ProjectInternal) sub).evaluate();
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelector.java b/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelector.java
index a1ade7b..c6d5579 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelector.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelector.java
@@ -23,12 +23,12 @@ import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.util.NameMatcher;
 
-import java.util.Map;
 import java.util.Set;
 
 public class TaskSelector {
     private final TaskNameResolver taskNameResolver;
     private final GradleInternal gradle;
+    private final ProjectFinderByTaskPath projectFinder = new ProjectFinderByTaskPath();
 
     public TaskSelector(GradleInternal gradle) {
         this(gradle, new TaskNameResolver());
@@ -47,9 +47,7 @@ public class TaskSelector {
         ProjectInternal project = gradle.getDefaultProject();
 
         if (path.contains(Project.PATH_SEPARATOR)) {
-            String projectPath = StringUtils.substringBeforeLast(path, Project.PATH_SEPARATOR);
-            projectPath = projectPath.length() == 0 ? Project.PATH_SEPARATOR : projectPath;
-            project = findProject(project, projectPath);
+            project = projectFinder.findProject(path, project);
             baseName = StringUtils.substringAfterLast(path, Project.PATH_SEPARATOR);
             prefix = project.getPath() + Project.PATH_SEPARATOR;
 
@@ -78,31 +76,6 @@ public class TaskSelector {
         throw new TaskSelectionException(matcher.formatErrorMessage("task", project));
     }
 
-    private static ProjectInternal findProject(ProjectInternal startFrom, String path) {
-        if (path.equals(Project.PATH_SEPARATOR)) {
-            return startFrom.getRootProject();
-        }
-        Project current = startFrom;
-        if (path.startsWith(Project.PATH_SEPARATOR)) {
-            current = current.getRootProject();
-            path = path.substring(1);
-        }
-        for (String pattern : path.split(Project.PATH_SEPARATOR)) {
-            Map<String, Project> children = current.getChildProjects();
-
-            NameMatcher matcher = new NameMatcher();
-            Project child = matcher.find(pattern, children);
-            if (child != null) {
-                current = child;
-                continue;
-            }
-
-            throw new TaskSelectionException(matcher.formatErrorMessage("project", current));
-        }
-
-        return (ProjectInternal) current;
-    }
-
     public static class TaskSelection {
         private String taskName;
         private Set<Task> tasks;
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/commandline/CommandLineTaskConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/execution/commandline/CommandLineTaskConfigurer.java
index 0876157..e7139cb 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/commandline/CommandLineTaskConfigurer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/commandline/CommandLineTaskConfigurer.java
@@ -38,7 +38,7 @@ public class CommandLineTaskConfigurer {
 
     public List<String> configureTasks(Collection<Task> tasks, List<String> arguments) {
         assert !tasks.isEmpty();
-        if (!shouldConfigureWith(arguments)) {
+        if (arguments.isEmpty()) {
             return arguments;
         }
         return configureTasksNow(tasks, arguments);
@@ -64,7 +64,7 @@ public class CommandLineTaskConfigurer {
                 }
             }
 
-            ParsedCommandLine parsed = null;
+            ParsedCommandLine parsed;
             try {
                 parsed = parser.parse(arguments);
             } catch (CommandLineArgumentException e) {
@@ -96,13 +96,4 @@ public class CommandLineTaskConfigurer {
         Class<?> type = method.getParameterTypes()[0];
         return type == Boolean.class || type == Boolean.TYPE;
     }
-
-    private boolean shouldConfigureWith(List<String> arguments) {
-        for (String a : arguments) {
-            if (a.startsWith("--")) {
-                return true;
-            }
-        }
-        return false;
-    }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/commandline/CommandLineTaskParser.java b/subprojects/core/src/main/groovy/org/gradle/execution/commandline/CommandLineTaskParser.java
index 1597655..291b5d7 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/commandline/CommandLineTaskParser.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/commandline/CommandLineTaskParser.java
@@ -19,7 +19,6 @@ package org.gradle.execution.commandline;
 import com.google.common.collect.LinkedHashMultimap;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.SetMultimap;
-import org.gradle.api.GradleException;
 import org.gradle.api.Task;
 import org.gradle.execution.TaskSelector;
 
@@ -35,7 +34,6 @@ public class CommandLineTaskParser {
     CommandLineTaskConfigurer taskConfigurer =  new CommandLineTaskConfigurer();
 
     public Multimap<String, Task> parseTasks(List<String> taskPaths, TaskSelector taskSelector) {
-        validateTaskPaths(taskPaths);
         SetMultimap<String, Task> out = LinkedHashMultimap.create();
         List<String> remainingPaths = new LinkedList<String>(taskPaths);
         while (!remainingPaths.isEmpty()) {
@@ -48,16 +46,4 @@ public class CommandLineTaskParser {
         }
         return out;
     }
-
-    private void validateTaskPaths(List<String> taskPaths) {
-        List<String> invalidTasks = new LinkedList<String>();
-        for (String path : taskPaths) {
-            if (path.startsWith("-") && !path.startsWith("--")) {
-                invalidTasks.add(path);
-            }
-        }
-        if (!invalidTasks.isEmpty()) {
-            throw new GradleException("Incorrect command line arguments: " + invalidTasks + ". Task options require double dash, for example: 'gradle tasks --all'.");
-        }
-    }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuter.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuter.java
index 92d6650..dddbae7 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuter.java
@@ -23,6 +23,7 @@ import org.gradle.api.execution.TaskExecutionListener;
 import org.gradle.api.specs.Spec;
 import org.gradle.execution.TaskFailureHandler;
 import org.gradle.execution.TaskGraphExecuter;
+import org.gradle.listener.ClosureBackedMethodInvocationDispatch;
 import org.gradle.listener.ListenerBroadcast;
 import org.gradle.listener.ListenerManager;
 import org.gradle.util.Clock;
@@ -96,7 +97,7 @@ public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
     }
 
     public void whenReady(final Closure closure) {
-        graphListeners.add("graphPopulated", closure);
+        graphListeners.add(new ClosureBackedMethodInvocationDispatch("graphPopulated", closure));
     }
 
     public void addTaskExecutionListener(TaskExecutionListener listener) {
@@ -108,11 +109,11 @@ public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
     }
 
     public void beforeTask(final Closure closure) {
-        taskListeners.add("beforeExecute", closure);
+        taskListeners.add(new ClosureBackedMethodInvocationDispatch("beforeExecute", closure));
     }
 
     public void afterTask(final Closure closure) {
-        taskListeners.add("afterExecute", closure);
+        taskListeners.add(new ClosureBackedMethodInvocationDispatch("afterExecute", closure));
     }
 
     public boolean hasTask(Task task) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandler.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandler.java
index e0549a1..3182a27 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandler.java
@@ -60,7 +60,7 @@ public class DefaultScriptCompilationHandler implements ScriptCompilationHandler
                              Transformer transformer, Class<? extends Script> scriptBaseClass) {
         Clock clock = new Clock();
         GFileUtils.deleteDirectory(classesDir);
-        classesDir.mkdirs();
+        GFileUtils.mkdirs(classesDir);
         CompilerConfiguration configuration = createBaseCompilerConfiguration(scriptBaseClass);
         configuration.setTargetDirectory(classesDir);
         try {
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/CompositeInitScriptFinder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/CompositeInitScriptFinder.java
index 7ced66c..5d2612e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/CompositeInitScriptFinder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/CompositeInitScriptFinder.java
@@ -15,9 +15,7 @@
  */
 package org.gradle.initialization;
 
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.groovy.scripts.ScriptSource;
-
+import java.io.File;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
@@ -29,9 +27,9 @@ public class CompositeInitScriptFinder implements InitScriptFinder {
         this.finders = Arrays.asList(finders);
     }
 
-    public void findScripts(GradleInternal gradle, Collection<ScriptSource> scripts) {
+    public void findScripts(Collection<File> scripts) {
         for (InitScriptFinder finder : finders) {
-            finder.findScripts(gradle, scripts);
+            finder.findScripts(scripts);
         }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultExceptionAnalyser.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultExceptionAnalyser.java
index cfa9c9d..77eb6c2 100755
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultExceptionAnalyser.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultExceptionAnalyser.java
@@ -46,7 +46,7 @@ public class DefaultExceptionAnalyser implements ExceptionAnalyser, ScriptExecut
     }
 
     public Throwable transform(Throwable exception) {
-        Throwable actualException = findDeepest(exception);
+        Throwable actualException = findDeepestRootException(exception);
         if (actualException == null) {
             return exception;
         }
@@ -85,7 +85,8 @@ public class DefaultExceptionAnalyser implements ExceptionAnalyser, ScriptExecut
         return new LocationAwareException(actualException, target, source, lineNumber);
     }
 
-    private Throwable findDeepest(Throwable exception) {
+    private Throwable findDeepestRootException(Throwable exception) {
+        // TODO: fix the way we work out which exception is important: TaskExecutionException is not always the most helpful
         Throwable locationAware = null;
         Throwable result = null;
         Throwable contextMatch = null;
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java
index ac459fe..d7b4a4c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java
@@ -140,8 +140,11 @@ public class DefaultGradleLauncher extends GradleLauncher {
 
         // Configure build
         buildConfigurer.configure(gradle);
-        buildListener.projectsEvaluated(gradle);
-        modelConfigurationListener.onConfigure(gradle);
+
+        if (!gradle.getStartParameter().isConfigureOnDemand()) {
+            buildListener.projectsEvaluated(gradle);
+            modelConfigurationListener.onConfigure(gradle);
+        }
 
         if (upTo == Stage.Configure) {
             return;
@@ -150,6 +153,10 @@ public class DefaultGradleLauncher extends GradleLauncher {
         // Populate task graph
         buildExecuter.select(gradle);
 
+        if (gradle.getStartParameter().isConfigureOnDemand()) {
+            buildListener.projectsEvaluated(gradle);
+        }
+
         if (upTo == Stage.PopulateTaskGraph) {
             return;
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DirectoryInitScriptFinder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DirectoryInitScriptFinder.java
index bde277b..fb74c5c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DirectoryInitScriptFinder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DirectoryInitScriptFinder.java
@@ -15,9 +15,6 @@
  */
 package org.gradle.initialization;
 
-import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.groovy.scripts.UriScriptSource;
-
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -25,7 +22,7 @@ import java.util.Collections;
 import java.util.List;
 
 public abstract class DirectoryInitScriptFinder implements InitScriptFinder {
-    protected void findScriptsInDir(File initScriptsDir, Collection<ScriptSource> scripts) {
+    protected void findScriptsInDir(File initScriptsDir, Collection<File> scripts) {
         if (!initScriptsDir.isDirectory()) {
             return;
         }
@@ -37,7 +34,7 @@ public abstract class DirectoryInitScriptFinder implements InitScriptFinder {
         }
         Collections.sort(files);
         for (File file : files) {
-            scripts.add(new UriScriptSource("initialization script", file));
+            scripts.add(file);
         }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DistributionInitScriptFinder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DistributionInitScriptFinder.java
index 5be76a0..64394aa 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DistributionInitScriptFinder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DistributionInitScriptFinder.java
@@ -15,10 +15,6 @@
  */
 package org.gradle.initialization;
 
-import org.gradle.api.internal.GradleDistributionLocator;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.groovy.scripts.ScriptSource;
-
 import java.io.File;
 import java.util.Collection;
 
@@ -26,18 +22,17 @@ import java.util.Collection;
  * An {@link InitScriptFinder} that includes every *.gradle file in $gradleHome/init.d.
  */
 public class DistributionInitScriptFinder extends DirectoryInitScriptFinder {
-    final GradleDistributionLocator locator;
+    final File gradleHome;
 
-    public DistributionInitScriptFinder(GradleDistributionLocator locator) {
-        this.locator = locator;
+    public DistributionInitScriptFinder(File gradleHome) {
+        this.gradleHome = gradleHome;
     }
 
-    public void findScripts(GradleInternal gradle, Collection<ScriptSource> scripts) {
-        File distDir = locator.getGradleHome();
-        if (distDir == null) {
+    public void findScripts(Collection<File> scripts) {
+        if (gradleHome == null) {
             return;
         }
-        findScriptsInDir(new File(distDir, "init.d"), scripts);
+        findScriptsInDir(new File(gradleHome, "init.d"), scripts);
     }
 
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/InitScriptFinder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/InitScriptFinder.java
index 6289572..dbf680f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/InitScriptFinder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/InitScriptFinder.java
@@ -15,14 +15,12 @@
  */
 package org.gradle.initialization;
 
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.groovy.scripts.ScriptSource;
-
+import java.io.File;
 import java.util.Collection;
 
 /**
  * Interface for objects that can find init scripts for a given build.
  */
 public interface InitScriptFinder {
-    public void findScripts(GradleInternal gradle, Collection<ScriptSource> scripts);
+    public void findScripts(Collection<File> scripts);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/InitScriptHandler.java b/subprojects/core/src/main/groovy/org/gradle/initialization/InitScriptHandler.java
index d41300d..4005225 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/InitScriptHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/InitScriptHandler.java
@@ -16,29 +16,24 @@
 package org.gradle.initialization;
 
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.configuration.InitScriptProcessor;
+import org.gradle.groovy.scripts.UriScriptSource;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.io.File;
 
 /**
  * Finds and executes all init scripts for a given build.
  */
 public class InitScriptHandler {
-    private final InitScriptFinder finder;
     private final InitScriptProcessor processor;
 
-    public InitScriptHandler(InitScriptFinder finder, InitScriptProcessor processor) {
-        this.finder = finder;
+    public InitScriptHandler(InitScriptProcessor processor) {
         this.processor = processor;
     }
 
     public void executeScripts(GradleInternal gradle) {
-        List<ScriptSource> scriptSources = new ArrayList<ScriptSource>();
-        finder.findScripts(gradle, scriptSources);
-        for (ScriptSource source : scriptSources) {
-            processor.process(source, gradle);
+        for (File script : gradle.getStartParameter().getAllInitScripts()) {
+            processor.process(new UriScriptSource("initialization script", script), gradle);
         }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ProvidedInitScriptFinder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ProvidedInitScriptFinder.java
deleted file mode 100644
index 757bce1..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/ProvidedInitScriptFinder.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.initialization;
-
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.groovy.scripts.UriScriptSource;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Simple finder that "finds" all the init scripts that were explicitly added to the start parameters.
- */
-public class ProvidedInitScriptFinder implements InitScriptFinder {
-    public void findScripts(GradleInternal gradle, Collection<ScriptSource> scripts) {
-        List<File> scriptFiles = gradle.getStartParameter().getInitScripts();
-        for (File file : scriptFiles) {
-            scripts.add(new UriScriptSource("initialization script", file));
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/UserHomeInitScriptFinder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/UserHomeInitScriptFinder.java
index 46e8ec6..c75c542 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/UserHomeInitScriptFinder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/UserHomeInitScriptFinder.java
@@ -15,19 +15,21 @@
  */
 package org.gradle.initialization;
 
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.groovy.scripts.UriScriptSource;
-
 import java.io.File;
 import java.util.Collection;
 
 public class UserHomeInitScriptFinder extends DirectoryInitScriptFinder implements InitScriptFinder {
-    public void findScripts(GradleInternal gradle, Collection<ScriptSource> scripts) {
-        File userHomeDir = gradle.getStartParameter().getGradleUserHomeDir();
+
+    private final File userHomeDir;
+
+    public UserHomeInitScriptFinder(File userHomeDir) {
+        this.userHomeDir = userHomeDir;
+    }
+
+    public void findScripts(Collection<File> scripts) {
         File userInitScript = new File(userHomeDir, "init.gradle");
         if (userInitScript.isFile()) {
-            scripts.add(new UriScriptSource("initialization script", userInitScript));
+            scripts.add(userInitScript);
         }
         findScriptsInDir(new File(userHomeDir, "init.d"), scripts);
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/invocation/DefaultGradle.java b/subprojects/core/src/main/groovy/org/gradle/invocation/DefaultGradle.java
index 3b0a39f..9b21679 100644
--- a/subprojects/core/src/main/groovy/org/gradle/invocation/DefaultGradle.java
+++ b/subprojects/core/src/main/groovy/org/gradle/invocation/DefaultGradle.java
@@ -31,6 +31,7 @@ import org.gradle.api.internal.project.ServiceRegistryFactory;
 import org.gradle.api.invocation.Gradle;
 import org.gradle.execution.TaskGraphExecuter;
 import org.gradle.listener.ActionBroadcast;
+import org.gradle.listener.ClosureBackedMethodInvocationDispatch;
 import org.gradle.listener.ListenerBroadcast;
 import org.gradle.listener.ListenerManager;
 import org.gradle.util.GradleVersion;
@@ -160,31 +161,31 @@ public class DefaultGradle implements GradleInternal {
     }
 
     public void beforeProject(Closure closure) {
-        projectEvaluationListenerBroadcast.add("beforeEvaluate", closure);
+        projectEvaluationListenerBroadcast.add(new ClosureBackedMethodInvocationDispatch("beforeEvaluate", closure));
     }
 
     public void afterProject(Closure closure) {
-        projectEvaluationListenerBroadcast.add("afterEvaluate", closure);
+        projectEvaluationListenerBroadcast.add(new ClosureBackedMethodInvocationDispatch("afterEvaluate", closure));
     }
 
     public void buildStarted(Closure closure) {
-        buildListenerBroadcast.add("buildStarted", closure);
+        buildListenerBroadcast.add(new ClosureBackedMethodInvocationDispatch("buildStarted", closure));
     }
 
     public void settingsEvaluated(Closure closure) {
-        buildListenerBroadcast.add("settingsEvaluated", closure);
+        buildListenerBroadcast.add(new ClosureBackedMethodInvocationDispatch("settingsEvaluated", closure));
     }
 
     public void projectsLoaded(Closure closure) {
-        buildListenerBroadcast.add("projectsLoaded", closure);
+        buildListenerBroadcast.add(new ClosureBackedMethodInvocationDispatch("projectsLoaded", closure));
     }
 
     public void projectsEvaluated(Closure closure) {
-        buildListenerBroadcast.add("projectsEvaluated", closure);
+        buildListenerBroadcast.add(new ClosureBackedMethodInvocationDispatch("projectsEvaluated", closure));
     }
 
     public void buildFinished(Closure closure) {
-        buildListenerBroadcast.add("buildFinished", closure);
+        buildListenerBroadcast.add(new ClosureBackedMethodInvocationDispatch("buildFinished", closure));
     }
 
     public void addListener(Object listener) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/ActionBroadcast.java b/subprojects/core/src/main/groovy/org/gradle/listener/ActionBroadcast.java
index 95afff0..a96c1b5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/listener/ActionBroadcast.java
+++ b/subprojects/core/src/main/groovy/org/gradle/listener/ActionBroadcast.java
@@ -15,7 +15,6 @@
  */
 package org.gradle.listener;
 
-import groovy.lang.Closure;
 import org.gradle.api.Action;
 
 public class ActionBroadcast<T> implements Action<T> {
@@ -29,7 +28,4 @@ public class ActionBroadcast<T> implements Action<T> {
         broadcast.add(action);
     }
 
-    public void add(Closure action) {
-        broadcast.add("execute", action);
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/BroadcastDispatch.java b/subprojects/core/src/main/groovy/org/gradle/listener/BroadcastDispatch.java
index 2492797..b5b5bd1 100755
--- a/subprojects/core/src/main/groovy/org/gradle/listener/BroadcastDispatch.java
+++ b/subprojects/core/src/main/groovy/org/gradle/listener/BroadcastDispatch.java
@@ -16,7 +16,6 @@
 
 package org.gradle.listener;
 
-import groovy.lang.Closure;
 import org.gradle.api.Action;
 import org.gradle.internal.UncheckedException;
 import org.gradle.messaging.dispatch.*;
@@ -25,7 +24,6 @@ import org.slf4j.LoggerFactory;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
@@ -51,11 +49,6 @@ public class BroadcastDispatch<T> implements Dispatch<MethodInvocation> {
         handlers.put(listener, new ReflectionDispatch(listener));
     }
 
-    public void add(String methodName, Closure closure) {
-        assertIsMethod(methodName);
-        handlers.put(closure, new ClosureInvocationHandler(methodName, closure));
-    }
-
     public void add(String methodName, Action<?> action) {
         assertIsMethod(methodName);
         handlers.put(action, new ActionInvocationHandler(methodName, action));
@@ -98,26 +91,6 @@ public class BroadcastDispatch<T> implements Dispatch<MethodInvocation> {
         }
     }
 
-    private class ClosureInvocationHandler implements Dispatch<MethodInvocation> {
-        private final String methodName;
-        private final Closure closure;
-
-        public ClosureInvocationHandler(String methodName, Closure closure) {
-            this.methodName = methodName;
-            this.closure = closure;
-        }
-
-        public void dispatch(MethodInvocation message) {
-            if (message.getMethod().getName().equals(methodName)) {
-                Object[] parameters = message.getArguments();
-                if (closure.getMaximumNumberOfParameters() < parameters.length) {
-                    parameters = Arrays.asList(parameters).subList(0, closure.getMaximumNumberOfParameters()).toArray();
-                }
-                closure.call(parameters);
-            }
-        }
-    }
-
     private class ActionInvocationHandler implements Dispatch<MethodInvocation> {
         private final String methodName;
         private final Action action;
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/ClosureBackedMethodInvocationDispatch.java b/subprojects/core/src/main/groovy/org/gradle/listener/ClosureBackedMethodInvocationDispatch.java
new file mode 100644
index 0000000..eba6424
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/listener/ClosureBackedMethodInvocationDispatch.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.listener;
+
+import groovy.lang.Closure;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+
+import java.util.Arrays;
+
+public class ClosureBackedMethodInvocationDispatch implements Dispatch<MethodInvocation> {
+    private final String methodName;
+    private final Closure closure;
+
+    public ClosureBackedMethodInvocationDispatch(String methodName, Closure closure) {
+        this.methodName = methodName;
+        this.closure = closure;
+    }
+
+    public void dispatch(MethodInvocation message) {
+        if (message.getMethod().getName().equals(methodName)) {
+            Object[] parameters = message.getArguments();
+            if (closure.getMaximumNumberOfParameters() < parameters.length) {
+                parameters = Arrays.asList(parameters).subList(0, closure.getMaximumNumberOfParameters()).toArray();
+            }
+            closure.call(parameters);
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        ClosureBackedMethodInvocationDispatch that = (ClosureBackedMethodInvocationDispatch) o;
+
+        if (!closure.equals(that.closure)) {
+            return false;
+        }
+        if (!methodName.equals(that.methodName)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = methodName.hashCode();
+        result = 31 * result + closure.hashCode();
+        return result;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/DefaultListenerManager.java b/subprojects/core/src/main/groovy/org/gradle/listener/DefaultListenerManager.java
index 548f3db..2d0aebd 100644
--- a/subprojects/core/src/main/groovy/org/gradle/listener/DefaultListenerManager.java
+++ b/subprojects/core/src/main/groovy/org/gradle/listener/DefaultListenerManager.java
@@ -21,7 +21,10 @@ import org.gradle.messaging.dispatch.Dispatch;
 import org.gradle.messaging.dispatch.MethodInvocation;
 import org.gradle.messaging.dispatch.ReflectionDispatch;
 
-import java.util.*;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
 
 @SuppressWarnings({"unchecked"})
 public class DefaultListenerManager implements ListenerManager {
@@ -134,7 +137,7 @@ public class DefaultListenerManager implements ListenerManager {
         if (listener instanceof ClosureListener) {
             ClosureListener closureListener = (ClosureListener) listener;
             if (broadcaster.getType().isAssignableFrom(closureListener.listenerType)) {
-                broadcaster.add(closureListener.method, closureListener.closure);
+                broadcaster.add(new ClosureBackedMethodInvocationDispatch(closureListener.method, closureListener.closure));
             }
         } else if (broadcaster.getType().isInstance(listener)) {
             broadcaster.add(listener);
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java b/subprojects/core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java
index 034c30c..3af54de 100644
--- a/subprojects/core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java
+++ b/subprojects/core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java
@@ -16,7 +16,6 @@
 
 package org.gradle.listener;
 
-import groovy.lang.Closure;
 import org.gradle.api.Action;
 import org.gradle.messaging.dispatch.Dispatch;
 import org.gradle.messaging.dispatch.MethodInvocation;
@@ -97,13 +96,6 @@ public class ListenerBroadcast<T> implements Dispatch<MethodInvocation> {
     public void add(Dispatch<MethodInvocation> dispatch) {
         broadcast.add(dispatch);
     }
-    
-    /**
-     * Adds a closure to be notified when the given method is called.
-     */
-    public void add(String methodName, Closure closure) {
-        broadcast.add(methodName, closure);
-    }
 
     /**
      * Adds an action to be executed when the given method is called.
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/OutputEvent.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/OutputEvent.java
index 288f0bc..f5af9ca 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/OutputEvent.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/OutputEvent.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.logging.internal;
 
+import org.gradle.api.Nullable;
 import org.gradle.api.logging.LogLevel;
 
 import java.io.Serializable;
@@ -24,7 +25,8 @@ import java.io.Serializable;
  */
 public abstract class OutputEvent implements Serializable {
     /**
-     * Returns the log level for this event. May return null.
+     * Returns the log level for this event.
      */
+    @Nullable
     public abstract LogLevel getLogLevel();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcess.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcess.java
index 7cab12c..b2e2ece 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcess.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcess.java
@@ -16,11 +16,11 @@
 
 package org.gradle.process.internal;
 
-import org.gradle.api.Action;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
+import org.gradle.internal.CompositeStoppable;
 import org.gradle.internal.UncheckedException;
-import org.gradle.messaging.remote.ConnectEvent;
+import org.gradle.messaging.remote.ConnectionAcceptor;
 import org.gradle.messaging.remote.ObjectConnection;
 import org.gradle.process.ExecResult;
 
@@ -35,6 +35,7 @@ public class DefaultWorkerProcess implements WorkerProcess {
     private final Lock lock = new ReentrantLock();
     private final Condition condition = lock.newCondition();
     private ObjectConnection connection;
+    private ConnectionAcceptor acceptor;
     private ExecHandle execHandle;
     private boolean running;
     private Throwable processFailure;
@@ -56,23 +57,29 @@ public class DefaultWorkerProcess implements WorkerProcess {
         });
     }
 
-    public Action<ConnectEvent<ObjectConnection>> getConnectAction() {
-        return new Action<ConnectEvent<ObjectConnection>>() {
-            public void execute(ConnectEvent<ObjectConnection> event) {
-                onConnect(event.getConnection());
-            }
-        };
+    public void startAccepting(ConnectionAcceptor acceptor) {
+        lock.lock();
+        try {
+            this.acceptor = acceptor;
+        } finally {
+            lock.unlock();
+        }
     }
 
-    private void onConnect(ObjectConnection connection) {
+    public void onConnect(ObjectConnection connection) {
+        ConnectionAcceptor stoppable;
+
         lock.lock();
         try {
             LOGGER.debug("Received connection {} from {}", connection, execHandle);
             this.connection = connection;
             condition.signalAll();
+            stoppable = acceptor;
         } finally {
             lock.unlock();
         }
+
+        stoppable.requestStop();
     }
 
     private void onProcessStop(ExecResult execResult) {
@@ -103,6 +110,15 @@ public class DefaultWorkerProcess implements WorkerProcess {
     }
 
     public void start() {
+        try {
+            doStart();
+        } catch (Throwable t) {
+            cleanup();
+            throw UncheckedException.throwAsUncheckedException(t);
+        }
+    }
+
+    private void doStart() {
         lock.lock();
         try {
             running = true;
@@ -136,19 +152,24 @@ public class DefaultWorkerProcess implements WorkerProcess {
     }
 
     public ExecResult waitForStop() {
-        ExecResult result = execHandle.waitForFinish();
-        ObjectConnection connection;
+        try {
+            return execHandle.waitForFinish().assertNormalExitValue();
+        } finally {
+            cleanup();
+        }
+    }
+
+    private void cleanup() {
+        CompositeStoppable stoppable;
         lock.lock();
         try {
-            connection = this.connection;
+            stoppable = CompositeStoppable.stoppable(acceptor, connection);
         } finally {
             this.connection = null;
+            this.acceptor = null;
             this.execHandle = null;
             lock.unlock();
         }
-        if (connection != null) {
-            connection.stop();
-        }
-        return result.assertNormalExitValue();
+        stoppable.stop();
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java
index 85b0f42..b4c7c3c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java
@@ -16,13 +16,13 @@
 
 package org.gradle.process.internal;
 
+import org.gradle.api.Action;
 import org.gradle.api.internal.ClassPathRegistry;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.internal.Factory;
 import org.gradle.internal.id.IdGenerator;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.messaging.remote.*;
 import org.gradle.process.internal.child.ApplicationClassesInIsolatedClassLoaderWorkerFactory;
 import org.gradle.process.internal.child.ApplicationClassesInSystemClassLoaderWorkerFactory;
 import org.gradle.process.internal.child.EncodedStream;
@@ -73,8 +73,14 @@ public class DefaultWorkerProcessFactory implements Factory<WorkerProcessBuilder
                 throw new IllegalStateException("No worker action specified for this worker process.");
             }
 
-            DefaultWorkerProcess workerProcess = new DefaultWorkerProcess(120, TimeUnit.SECONDS);
-            Address localAddress = server.accept(workerProcess.getConnectAction());
+            final DefaultWorkerProcess workerProcess = new DefaultWorkerProcess(120, TimeUnit.SECONDS);
+            ConnectionAcceptor acceptor = server.accept(new Action<ConnectEvent<ObjectConnection>>() {
+                public void execute(ConnectEvent<ObjectConnection> event) {
+                    workerProcess.onConnect(event.getConnection());
+                }
+            });
+            workerProcess.startAccepting(acceptor);
+            Address localAddress = acceptor.getAddress();
 
             // Build configuration for GradleWorkerMain
             List<URL> implementationClassPath = ClasspathUtil.getClasspath(getWorker().getClass().getClassLoader());
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ActionExecutionWorker.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ActionExecutionWorker.java
index 968cd50..c542421 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ActionExecutionWorker.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ActionExecutionWorker.java
@@ -49,36 +49,40 @@ public class ActionExecutionWorker implements Action<WorkerContext>, Serializabl
 
     public void execute(final WorkerContext workerContext) {
         MessagingServices messagingServices = createClient();
-        final MessagingClient client = messagingServices.get(MessagingClient.class);
-        final ObjectConnection clientConnection = client.getConnection(serverAddress);
         try {
-            LOGGER.debug("Starting {}.", displayName);
-            WorkerProcessContext context = new WorkerProcessContext() {
-                public ObjectConnection getServerConnection() {
-                    return clientConnection;
-                }
+            final MessagingClient client = messagingServices.get(MessagingClient.class);
+            final ObjectConnection clientConnection = client.getConnection(serverAddress);
+            try {
+                LOGGER.debug("Starting {}.", displayName);
+                WorkerProcessContext context = new WorkerProcessContext() {
+                    public ObjectConnection getServerConnection() {
+                        return clientConnection;
+                    }
 
-                public ClassLoader getApplicationClassLoader() {
-                    return workerContext.getApplicationClassLoader();
-                }
+                    public ClassLoader getApplicationClassLoader() {
+                        return workerContext.getApplicationClassLoader();
+                    }
 
-                public Object getWorkerId() {
-                    return workerId;
-                }
+                    public Object getWorkerId() {
+                        return workerId;
+                    }
 
-                public String getDisplayName() {
-                    return displayName;
-                }
-            };
+                    public String getDisplayName() {
+                        return displayName;
+                    }
+                };
 
-            ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
-            Thread.currentThread().setContextClassLoader(action.getClass().getClassLoader());
-            try {
-                action.execute(context);
+                ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+                Thread.currentThread().setContextClassLoader(action.getClass().getClassLoader());
+                try {
+                    action.execute(context);
+                } finally {
+                    Thread.currentThread().setContextClassLoader(contextClassLoader);
+                }
+                LOGGER.debug("Completed {}.", displayName);
             } finally {
-                Thread.currentThread().setContextClassLoader(contextClassLoader);
+                clientConnection.stop();
             }
-            LOGGER.debug("Completed {}.", displayName);
         } finally {
             LOGGER.debug("Stopping client connection.");
             messagingServices.stop();
diff --git a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryCacheFactory.java b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryCacheFactory.java
index e6cfd1f..3f802b2 100644
--- a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryCacheFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryCacheFactory.java
@@ -17,11 +17,13 @@ package org.gradle.testfixtures.internal;
 
 import org.gradle.CacheUsage;
 import org.gradle.api.Action;
-import org.gradle.internal.Factory;
 import org.gradle.api.internal.changedetection.InMemoryIndexedCache;
 import org.gradle.cache.*;
 import org.gradle.cache.internal.*;
+import org.gradle.internal.Factory;
+import org.gradle.messaging.serialize.DefaultSerializer;
 import org.gradle.messaging.serialize.Serializer;
+import org.gradle.util.GFileUtils;
 
 import java.io.File;
 import java.util.Collections;
@@ -33,7 +35,7 @@ public class InMemoryCacheFactory implements CacheFactory {
     }
 
     public PersistentCache open(File cacheDir, String displayName, CacheUsage usage, CacheValidator cacheValidator, Map<String, ?> properties, FileLockManager.LockMode lockMode, Action<? super PersistentCache> initializer) {
-        cacheDir.mkdirs();
+        GFileUtils.mkdirs(cacheDir);
         InMemoryCache cache = new InMemoryCache(cacheDir);
         if (initializer != null) {
             initializer.execute(cache);
@@ -42,11 +44,11 @@ public class InMemoryCacheFactory implements CacheFactory {
     }
 
     public <K, V> PersistentIndexedCache<K, V> openIndexedCache(File cacheDir, CacheUsage usage, CacheValidator validator, Map<String, ?> properties, FileLockManager.LockMode lockMode, Serializer<V> serializer) {
-        return new InMemoryIndexedCache<K, V>();
+        return new InMemoryIndexedCache<K, V>(serializer);
     }
 
     public <E> PersistentStateCache<E> openStateCache(File cacheDir, CacheUsage usage, CacheValidator validator, Map<String, ?> properties, FileLockManager.LockMode lockMode, Serializer<E> serializer) {
-        cacheDir.mkdirs();
+        GFileUtils.mkdirs(cacheDir);
         return new SimpleStateCache<E>(new File(cacheDir, "state.bin"), new NoOpFileLock(), new DefaultSerializer<E>());
     }
 
@@ -79,11 +81,15 @@ public class InMemoryCacheFactory implements CacheFactory {
         }
 
         public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Class<V> valueType) {
-            return new InMemoryIndexedCache<K, V>();
+            return new InMemoryIndexedCache<K, V>(new DefaultSerializer<V>());
         }
 
         public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Serializer<V> valueSerializer) {
-            return new InMemoryIndexedCache<K, V>();
+            return new InMemoryIndexedCache<K, V>(valueSerializer);
+        }
+
+        public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
+            return new InMemoryIndexedCache<K, V>(valueSerializer);
         }
 
         public <T> T useCache(String operationDisplayName, Factory<? extends T> action) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ConfigureUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/ConfigureUtil.java
index 85b78d8..f70c538 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/ConfigureUtil.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/ConfigureUtil.java
@@ -25,7 +25,7 @@ import org.gradle.api.internal.DynamicObjectUtil;
 import java.util.Collection;
 import java.util.Map;
 
-import static org.apache.commons.collections.CollectionUtils.subtract;
+import static org.gradle.util.CollectionUtils.toStringList;
 
 /**
  * @author Hans Dockter
@@ -53,10 +53,13 @@ public class ConfigureUtil {
         return delegate;
     }
 
-    public static <T> T configureByMap(Map<String, ?> properties, T delegate, Collection<String> mandatoryKeys) {
-        Collection missingKeys = subtract(mandatoryKeys, properties.keySet());
-        if(!missingKeys.isEmpty()) {
-            throw new IncompleteInputException("Input configuration map does not contain following mandatory keys: " + missingKeys, missingKeys);
+    public static <T> T configureByMap(Map<?, ?> properties, T delegate, Collection<?> mandatoryKeys) {
+        if (!mandatoryKeys.isEmpty()) {
+            Collection<String> missingKeys = toStringList(mandatoryKeys);
+            missingKeys.removeAll(toStringList(properties.keySet()));
+            if (!missingKeys.isEmpty()) {
+                throw new IncompleteInputException("Input configuration map does not contain following mandatory keys: " + missingKeys, missingKeys);
+            }
         }
         return configureByMap(properties, delegate);
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/GFileUtils.java b/subprojects/core/src/main/groovy/org/gradle/util/GFileUtils.java
index 810db05..a08f5d9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/GFileUtils.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/GFileUtils.java
@@ -219,15 +219,6 @@ public class GFileUtils {
             throw new UncheckedIOException(e);
         }
     }
-    /**
-     * Creates a directory and any unexisting parent directories. Throws an
-     * UncheckedIOException if it fails to do so.
-     */
-    public static void createDirectory(File directory) {
-        if (!directory.mkdirs() && !directory.isDirectory()) {
-            throw new UncheckedIOException("Failed to create directory " + directory);
-        }
-    }
 
     /**
      * Returns a relative path from 'from' to 'to'
@@ -290,12 +281,12 @@ public class GFileUtils {
                 throw new UncheckedIOException(String.format("Cannot create parent directory '%s' when creating directory '%s' as '%s' is not a directory", parentDirToCreate, dir, parentDirToCreateParent));
             }
 
-            if (!parentDirToCreate.mkdir()) {
+            if (!parentDirToCreate.mkdir() && !parentDirToCreate.isDirectory()) {
                 throw new UncheckedIOException(String.format("Failed to create parent directory '%s' when creating directory '%s'", parentDirToCreate, dir));
             }
         }
 
-        if (!dir.mkdirs()) {
+        if (!dir.mkdir() && !dir.isDirectory()) {
             throw new UncheckedIOException(String.format("Failed to create directory '%s'", dir));
         }
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/GradleVersion.java b/subprojects/core/src/main/groovy/org/gradle/util/GradleVersion.java
index 84f3713..674eb14 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/GradleVersion.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/GradleVersion.java
@@ -182,20 +182,10 @@ public class GradleVersion implements Comparable<GradleVersion> {
         }
     }
 
-    private boolean isNonSymbolicNumber() {
-        return versionPart.equals("0.0");
-    }
-
     public int compareTo(GradleVersion gradleVersion) {
         assertCanQueryParts();
         gradleVersion.assertCanQueryParts();
 
-        if (isNonSymbolicNumber() && !gradleVersion.isNonSymbolicNumber()) {
-            return 1;
-        } else if (!isNonSymbolicNumber() && gradleVersion.isNonSymbolicNumber()) {
-            return -1;
-        }
-
         String[] majorVersionParts = versionPart.split("\\.");
         String[] otherMajorVersionParts = gradleVersion.versionPart.split("\\.");
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/Jvm.java b/subprojects/core/src/main/groovy/org/gradle/util/Jvm.java
index 3349792..ff0dcab 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/Jvm.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/Jvm.java
@@ -83,10 +83,6 @@ public class Jvm implements JavaInfo {
         return jvm.getInheritableEnvironmentVariables(envVars);
     }
 
-    public boolean getSupportsAppleScript() {
-        return jvm.getSupportsAppleScript();
-    }
-
     public boolean isIbmJvm() {
         return jvm.isIbmJvm();
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/TextUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/TextUtil.java
index a921af0..c7f6c9e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/TextUtil.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/TextUtil.java
@@ -68,9 +68,8 @@ public class TextUtil {
     }
 
     /**
-     * <p>Escapes the toString() representation of {@code obj} for use in a literal string.</p>
-     *
-     * <p>This is useful for interpolating variables into script strings, as well as in other situations.</p>
+     * Escapes the toString() representation of {@code obj} for use in a literal string.
+     * This is useful for interpolating variables into script strings, as well as in other situations.
      */
     public static String escapeString(Object obj) {
         return obj == null ? null : StringEscapeUtils.escapeJava(obj.toString());
@@ -109,4 +108,4 @@ public class TextUtil {
 
         return builder.toString();
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/hash/HashValue.java b/subprojects/core/src/main/groovy/org/gradle/util/hash/HashValue.java
index 379be6e..155f01b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/hash/HashValue.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/hash/HashValue.java
@@ -23,18 +23,18 @@ public class HashValue {
     public HashValue(byte[] digest) {
         this.digest = new BigInteger(1, digest);
     }
-    
+
     public HashValue(String hexString) {
         this.digest = new BigInteger(hexString, 16);
     }
-    
+
     public static HashValue parse(String inputString) {
         if (inputString == null || inputString.length() == 0) {
             return null;
         }
         return new HashValue(parseInput(inputString));
     }
-    
+
     private static String parseInput(String inputString) {
         if (inputString == null) {
             return null;
@@ -57,15 +57,19 @@ public class HashValue {
     public String asCompactString() {
         return digest.toString(32);
     }
-    
+
     public String asHexString() {
-        return digest.toString(16);        
+        return digest.toString(16);
     }
 
     public byte[] asByteArray() {
         return digest.toByteArray();
     }
 
+    public BigInteger asBigInteger() {
+        return digest;
+    }
+
     @Override
     public boolean equals(Object other) {
         if (this == other) {
diff --git a/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy
index 93e02d7..9928867 100644
--- a/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy
@@ -17,10 +17,11 @@
 package org.gradle
 
 import org.gradle.api.logging.LogLevel
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.SetSystemProperties
-import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import org.junit.Test
+
 import static org.gradle.util.Matchers.*
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
@@ -30,7 +31,7 @@ import static org.junit.Assert.*
  */
 class StartParameterTest {
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     @Rule
     public SetSystemProperties systemProperties = new SetSystemProperties()
 
@@ -229,4 +230,29 @@ class StartParameterTest {
         assert p(a: "2", b: "3") == [a: "2", b: "3"]
         assert p() == [a: "1"]
     }
+
+    @Test
+    public void testGetAllInitScripts() {
+        def gradleUserHomeDir = tmpDir.testDirectory.createDir("gradleUserHomeDie")
+        def gradleHomeDir = tmpDir.testDirectory.createDir("gradleHomeDir")
+        StartParameter parameter = new StartParameter()
+
+        parameter.gradleUserHomeDir = gradleUserHomeDir
+        parameter.gradleHomeDir = gradleHomeDir
+
+        assert parameter.allInitScripts.empty
+
+        def userMainInit = gradleUserHomeDir.createFile("init.gradle")
+        assert parameter.allInitScripts == [userMainInit]
+
+        def userInit1 = gradleUserHomeDir.createFile("init.d/1.gradle")
+        def userInit2 = gradleUserHomeDir.createFile("init.d/2.gradle")
+
+        assert parameter.allInitScripts == [userMainInit, userInit1, userInit2]
+
+        def distroInit1 = gradleHomeDir.createFile("init.d/1.gradle")
+        def distroInit2 = gradleHomeDir.createFile("init.d/2.gradle")
+
+        assert parameter.allInitScripts == [userMainInit, userInit1, userInit2, distroInit1, distroInit2]
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractTaskSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractTaskSpec.groovy
new file mode 100644
index 0000000..d7607f0
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractTaskSpec.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal
+
+import org.gradle.api.Action
+import org.gradle.api.Task
+import org.gradle.api.internal.project.AbstractProject
+import org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory
+import org.gradle.api.internal.project.taskfactory.TaskFactory
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.internal.service.DefaultServiceRegistry
+import org.gradle.util.GUtil
+import org.gradle.util.HelperUtil
+import org.junit.Test
+import spock.lang.Specification
+
+import static org.junit.Assert.assertTrue
+
+class AbstractTaskSpec extends Specification {
+
+    private DefaultServiceRegistry serviceRegistry = new DefaultServiceRegistry();
+    private Instantiator instantiator = new DependencyInjectingInstantiator(serviceRegistry);
+    private final AnnotationProcessingTaskFactory rootFactory = new AnnotationProcessingTaskFactory(new TaskFactory(new AsmBackedClassGenerator()));
+
+    public static class TestTask extends AbstractTask {
+
+    }
+
+    public Task createTask(String name) {
+        AbstractProject project = HelperUtil.createRootProject();
+        DefaultServiceRegistry registry = new DefaultServiceRegistry();
+        registry.add(Instantiator.class, new DirectInstantiator());
+        Task task = rootFactory.createChild(project, instantiator).createTask(GUtil.map(Task.TASK_TYPE, TestTask.class, Task.TASK_NAME, name));
+        assertTrue(TestTask.class.isAssignableFrom(task.getClass()));
+        return TestTask.class.cast(task);
+    }
+
+    @Test
+    def "can add action to a task via Task.getActions() List"() {
+        setup:
+        TestTask task = createTask("task")
+        when:
+        def actions = task.getActions()
+        and:
+        def action = Mock(Action)
+
+        actions.add(action)
+        then:
+        task.actions.size() == 1
+        actions.size() == 1
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/ClosureBackedActionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/ClosureBackedActionTest.groovy
new file mode 100644
index 0000000..7a1fb4c
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/ClosureBackedActionTest.groovy
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal
+
+import org.gradle.api.Action
+import org.gradle.api.InvalidActionClosureException
+import spock.lang.Specification
+
+class ClosureBackedActionTest extends Specification {
+
+    def "one arg closure is called"() {
+        given:
+        def called = false
+        def thing = "1"
+        def closure = {
+            called = true
+            assert it.is(thing)
+            assert delegate.is(thing)
+        }
+
+        when:
+        action(closure).execute(thing)
+
+        then:
+        called
+    }
+
+    def "zero arg closure is called"() {
+        given:
+        def called = false
+        def thing = "1"
+        def closure = { ->
+            called = true
+            assert delegate.is(thing)
+        }
+
+        when:
+        action(closure).execute(thing)
+
+        then:
+        called
+    }
+
+    def "closure with wrong param type is given"() {
+        given:
+        def closure = { Map m -> }
+        def arg = "1"
+
+        when:
+        action(closure).execute(arg)
+
+        then:
+        def e = thrown InvalidActionClosureException
+        e.closure.is(closure)
+        e.argument.is(arg)
+        e.message == "The closure '${closure.toString()}' is not valid as an action for argument '1'. It should accept no parameters, or one compatible with type 'java.lang.String'. It accepts (java.util.Map)."
+    }
+
+    def "closure with more than one param type is given"() {
+        given:
+        def closure = { Map m, List l -> }
+        def arg = "1"
+
+        when:
+        action(closure).execute(arg)
+
+        then:
+        def e = thrown InvalidActionClosureException
+        e.closure.is(closure)
+        e.argument.is(arg)
+        e.message == "The closure '${closure.toString()}' is not valid as an action for argument '1'. It should accept no parameters, or one compatible with type 'java.lang.String'. It accepts (java.util.Map, java.util.List)."
+    }
+
+    def "equality"() {
+        given:
+        def c = {}
+        def a1 = action(c)
+
+        expect:
+        a1 == action(c)
+        a1 != new ClosureBackedAction(c, Closure.OWNER_ONLY)
+        a1 != new ClosureBackedAction(c, Closure.DELEGATE_FIRST, false)
+        a1 != action({})
+    }
+
+    Action<?> action(Closure<?> c) {
+        new ClosureBackedAction(c)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/ConfigureByMapActionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/ConfigureByMapActionTest.groovy
new file mode 100644
index 0000000..38d42d9
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/ConfigureByMapActionTest.groovy
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal
+
+import org.gradle.util.ConfigureUtil
+import spock.lang.Specification
+
+class ConfigureByMapActionTest extends Specification {
+
+    ConfigureByMapAction action(Object[] args) {
+        new ConfigureByMapAction(*args)        
+    }
+
+    def canConfigureObjectPropertyUsingMap() {
+        given:
+        Bean obj = new Bean()
+
+        when:
+        action(prop: 'value').execute(obj)
+
+        then:
+        obj.prop == "value"
+
+        when:
+        action(method: 'value2').execute(obj)
+
+        then:
+        obj.prop == 'value2'
+    }
+
+    def canConfigureAndValidateObjectUsingMap() {
+        given:
+        Bean obj = new Bean()
+
+        when:
+        action(prop: 'value', ['foo']).execute(obj)
+
+        then:
+        def e = thrown(ConfigureUtil.IncompleteInputException)
+        e.missingKeys.contains("foo")
+
+        when:
+        action([prop: 'value'], ['prop']).execute(obj)
+
+        then:
+        assert obj.prop == 'value'
+    }
+
+    def canConfigureAndValidateObjectUsingMapUsingGstrings() {
+        given:
+        Bean obj = new Bean()
+        def prop = "prop"
+        def foo = "foo"
+
+        when:
+        action(["$prop": 'value'], ["$foo"]).execute(obj)
+
+        then:
+        def e = thrown(ConfigureUtil.IncompleteInputException)
+        e.missingKeys.contains("foo")
+
+        when:
+        action(["$prop": 'value'], ["$prop"]).execute(obj)
+
+        then:
+        assert obj.prop == 'value'
+    }
+
+    def throwsExceptionForUnknownProperty() {
+        given:
+        Bean obj = new Bean()
+
+        when:
+        action(unknown: 'value').execute(obj)
+
+        then:
+        def e = thrown(MissingPropertyException)
+        e.type == Bean
+        e.property == 'unknown'
+    }
+
+}
+
+class Bean {
+    String prop
+    def method(String value) {
+        prop = value
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultTaskTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultTaskTest.groovy
index a90bd70..11be2ae 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultTaskTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultTaskTest.groovy
@@ -18,16 +18,23 @@ package org.gradle.api.internal
 
 import org.gradle.api.Action
 import org.gradle.api.DefaultTask
+import org.gradle.api.Project
 import org.gradle.api.Task
 import org.gradle.api.tasks.AbstractTaskTest
+import org.gradle.api.tasks.TaskDependency
 import org.gradle.api.tasks.TaskExecutionException
+import org.gradle.api.tasks.TaskInstantiationException
 import org.gradle.listener.ListenerManager
+import org.gradle.util.WrapUtil
+import org.jmock.Expectations
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
+import spock.lang.Issue
 
 import java.util.concurrent.Callable
 
+import static org.gradle.util.Matchers.dependsOn
 import static org.gradle.util.Matchers.isEmpty
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
@@ -56,8 +63,9 @@ class DefaultTaskTest extends AbstractTaskTest {
     }
 
     @Test public void testDefaultTask() {
-        assertThat(defaultTask.dependsOn, isEmpty())
-        assertEquals([], defaultTask.actions)
+        DefaultTask task = AbstractTask.injectIntoNewInstance(project, TEST_TASK_NAME, { new DefaultTask() } as Callable)
+        assertThat(task.dependsOn, isEmpty())
+        assertEquals([], task.actions)
     }
 
     @Test public void testHasUsefulToString() {
@@ -71,6 +79,19 @@ class DefaultTaskTest extends AbstractTaskTest {
     }
 
     @Test
+    public void testDependsOn() {
+        Task dependsOnTask = createTask(project, "somename");
+        Task task = createTask(project, TEST_TASK_NAME);
+        project.getTasks().add("path1");
+        project.getTasks().add("path2");
+
+        task.dependsOn(Project.PATH_SEPARATOR + "path1");
+        assertThat(task, dependsOn("path1"));
+        task.dependsOn("path2", dependsOnTask);
+        assertThat(task, dependsOn("path1", "path2", "somename"));
+    }
+
+    @Test
     public void testConfigure() {
         Closure action1 = { Task t -> }
         assertSame(task, task.configure {
@@ -229,6 +250,39 @@ class DefaultTaskTest extends AbstractTaskTest {
     void canAccessServices() {
         assertNotNull(defaultTask.services.get(ListenerManager))
     }
+
+    @Test
+    public void testDependentTaskDidWork() {
+        final Task task1 = context.mock(Task.class, "task1");
+        final Task task2 = context.mock(Task.class, "task2");
+        final TaskDependency dependencyMock = context.mock(TaskDependency.class);
+        getTask().dependsOn(dependencyMock);
+        context.checking(new Expectations() {{
+            allowing(dependencyMock).getDependencies(getTask());
+            will(returnValue(WrapUtil.toSet(task1, task2)));
+
+            exactly(2).of(task1).getDidWork();
+            will(returnValue(false));
+
+            exactly(2).of(task2).getDidWork();
+            will(onConsecutiveCalls(returnValue(false), returnValue(true)));
+        }});
+
+        assertFalse(getTask().dependsOnTaskDidWork());
+
+        assertTrue(getTask().dependsOnTaskDidWork());
+    }
+
+    @Test
+    @Issue("http://issues.gradle.org/browse/GRADLE-2022")
+    public void testGoodErrorMessageWhenTaskInstantiatedDirectly() {
+        try {
+            new DefaultTask();
+            fail();
+        } catch (TaskInstantiationException e) {
+            assertThat(e.getMessage(), containsString("has been instantiated directly which is not supported"));
+        }
+    }
 }
 
 class TestConvention {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DocumentationRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DocumentationRegistryTest.groovy
index 2a3c416..e5b6e70 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DocumentationRegistryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DocumentationRegistryTest.groovy
@@ -16,13 +16,13 @@
 
 package org.gradle.api.internal
 
-import spock.lang.Specification
-import org.junit.Rule
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.GradleVersion
+import org.junit.Rule
+import spock.lang.Specification
 
 class DocumentationRegistryTest extends Specification {
-    @Rule TemporaryFolder tmpDir
+    @Rule TestNameTestDirectoryProvider tmpDir
     final GradleDistributionLocator locator = Mock()
     final GradleVersion gradleVersion = GradleVersion.current()
     final DocumentationRegistry registry = new DocumentationRegistry(locator, gradleVersion)
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/LocationAwareExceptionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/LocationAwareExceptionTest.groovy
index b11f726..696ccdc 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/LocationAwareExceptionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/LocationAwareExceptionTest.groovy
@@ -87,6 +87,35 @@ class LocationAwareExceptionTest extends Specification {
         e.reportableCauses == [childCause]
     }
 
+    def "visit reportable causes visits all contextual exceptions and direct cause of last contextual exception"() {
+        TreeVisitor visitor = Mock()
+        def unreportedCause = new RuntimeException()
+        def reportedCause = new RuntimeException(unreportedCause)
+        def lastContextual = new TestContextualException(reportedCause)
+        def interveningUnreported = new RuntimeException(lastContextual)
+        def contextual = new TestContextualException(interveningUnreported)
+        def interveningUnreported2 = new RuntimeException(contextual)
+        def cause = new TestContextualException(interveningUnreported2)
+        def e = new LocationAwareException(cause, cause, null, 100)
+
+        when:
+        e.visitReportableCauses(visitor)
+
+        then:
+        1 * visitor.node(e)
+        1 * visitor.node(contextual)
+        1 * visitor.node(lastContextual)
+        1 * visitor.node(reportedCause)
+
+        and:
+        _ * visitor.startChildren()
+        _ * visitor.endChildren()
+        0 * visitor._
+
+        and:
+        e.reportableCauses == [contextual, lastContextual, reportedCause]
+    }
+
     def "visit reportable causes visits causes of multi-cause exception"() {
         TreeVisitor visitor = Mock()
         def childCause1 = new RuntimeException()
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/PropertiesTransformerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/PropertiesTransformerTest.groovy
index 2c017c8..afa2a90 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/PropertiesTransformerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/PropertiesTransformerTest.groovy
@@ -50,7 +50,7 @@ class PropertiesTransformerTest extends Specification {
     
     def 'can use closure as action'() {
         given:
-        transformer.addAction { Properties props ->
+        transformer.addAction action { Properties props ->
             props.added = 'value'
         }
         when:
@@ -61,13 +61,13 @@ class PropertiesTransformerTest extends Specification {
     
     def 'can chain actions'() {
         given:
-        transformer.addAction { Properties props ->
+        transformer.addAction action { Properties props ->
             props.remove('removed')
         }
-        transformer.addAction { Properties props ->
+        transformer.addAction action { Properties props ->
             props.changed = 'new'
         }
-        transformer.addAction { Properties props ->
+        transformer.addAction action { Properties props ->
             props.added = 'value'
         }
         Properties original = props(removed:'value', changed:'old')
@@ -79,7 +79,7 @@ class PropertiesTransformerTest extends Specification {
     
     def 'can transform to an OutputStream'() {
         given:
-        transformer.addAction { Properties props ->
+        transformer.addAction action { Properties props ->
             props.added = 'value'
         }
         ByteArrayOutputStream outstr = new ByteArrayOutputStream()
@@ -98,4 +98,8 @@ class PropertiesTransformerTest extends Specification {
         props.putAll(map)
         return props
     }
+
+    Action action(Closure c) {
+        new ClosureBackedAction(c)
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/XmlTransformerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/XmlTransformerTest.groovy
deleted file mode 100644
index 8f913ce..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/XmlTransformerTest.groovy
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal
-
-import org.gradle.api.Action
-import org.gradle.api.XmlProvider
-import org.gradle.util.TextUtil
-import spock.lang.Specification
-
-class XmlTransformerTest extends Specification {
-    final XmlTransformer transformer = new XmlTransformer()
-
-    def "returns original string when no actions are provided"() {
-        expect:
-        looksLike '<root/>', transformer.transform('<root/>')
-    }
-
-    def "action can access XML as StringBuilder"() {
-        Action<XmlProvider> action = Mock()
-        transformer.addAction(action)
-
-        when:
-        def result = transformer.transform('<root/>')
-
-        then:
-        action.execute(_) >> { XmlProvider provider ->
-            def builder = provider.asString()
-            builder.insert(builder.indexOf("root"), 'some-')
-        }
-        looksLike '<some-root/>', result
-    }
-
-    def "action can access XML as Node"() {
-        Action<XmlProvider> action = Mock()
-        transformer.addAction(action)
-
-        when:
-        def result = transformer.transform('<root/>')
-
-        then:
-        action.execute(_) >> { XmlProvider provider ->
-            provider.asNode().appendNode('child1')
-        }
-        looksLike '<root>\n  <child1/>\n</root>\n', result
-    }
-
-    def "action can access XML as DOM element"() {
-        Action<XmlProvider> action = Mock()
-        transformer.addAction(action)
-
-        when:
-        def result = transformer.transform('<root/>')
-
-        then:
-        action.execute(_) >> { XmlProvider provider ->
-            def document = provider.asElement().ownerDocument
-            provider.asElement().appendChild(document.createElement('child1'))
-        }
-        looksLike '<root>\n  <child1/>\n</root>\n', result
-    }
-
-    def "can transform String to a Writer"() {
-        Action<XmlProvider> action = Mock()
-        transformer.addAction(action)
-        StringWriter writer = new StringWriter()
-
-        when:
-        transformer.transform('<root/>', writer)
-
-        then:
-        action.execute(_) >> { XmlProvider provider ->
-            provider.asNode().appendNode('child1')
-        }
-        looksLike '<root>\n  <child1/>\n</root>\n', writer.toString()
-    }
-
-    def "can transform Node to a Writer"() {
-        Action<XmlProvider> action = Mock()
-        transformer.addAction(action)
-        StringWriter writer = new StringWriter()
-        Node node = new XmlParser().parseText('<root/>')
-
-        when:
-        transformer.transform(node, writer)
-
-        then:
-        action.execute(_) >> { XmlProvider provider ->
-            provider.asNode().appendNode('child1')
-        }
-        looksLike '<root>\n  <child1/>\n</root>\n', writer.toString()
-    }
-
-    def "can use a closure as an action"() {
-        transformer.addAction { provider ->
-            provider.asNode().appendNode('child1')
-        }
-        StringWriter writer = new StringWriter()
-
-        when:
-        transformer.transform('<root/>', writer)
-
-        then:
-        looksLike '<root>\n  <child1/>\n</root>\n', writer.toString()
-    }
-
-    def "can chain actions"() {
-        Action<XmlProvider> stringAction = Mock()
-        Action<XmlProvider> nodeAction = Mock()
-        Action<XmlProvider> elementAction = Mock()
-        Action<XmlProvider> stringAction2 = Mock()
-        transformer.addAction(stringAction)
-        transformer.addAction(elementAction)
-        transformer.addAction(nodeAction)
-        transformer.addAction(stringAction2)
-
-        when:
-        def result = transformer.transform('<root/>')
-
-        then:
-        stringAction.execute(_) >> { XmlProvider provider ->
-            def builder = provider.asString()
-            builder.insert(builder.indexOf("root"), 'some-')
-        }
-        nodeAction.execute(_) >> { XmlProvider provider ->
-            provider.asNode().appendNode('child2')
-        }
-        elementAction.execute(_) >> { XmlProvider provider ->
-            def document = provider.asElement().ownerDocument
-            provider.asElement().appendChild(document.createElement('child1'))
-        }
-        stringAction2.execute(_) >> { XmlProvider provider ->
-            provider.asString().append('<!-- end -->')
-        }
-
-        looksLike '<some-root>\n  <child1/>\n  <child2/>\n</some-root>\n<!-- end -->', result
-    }
-
-    def "can chain node actions"() {
-        Action<XmlProvider> nodeAction = Mock()
-        Action<XmlProvider> nodeAction2 = Mock()
-        transformer.addAction(nodeAction)
-        transformer.addAction(nodeAction2)
-
-        when:
-        def result = transformer.transform('<root/>')
-
-        then:
-        nodeAction.execute(_) >> { XmlProvider provider ->
-            provider.asNode().appendNode('child1')
-        }
-        nodeAction2.execute(_) >> { XmlProvider provider ->
-            provider.asNode().appendNode('child2')
-        }
-        looksLike '<root>\n  <child1/>\n  <child2/>\n</root>\n', result
-    }
-
-    def "indentation correct when writing out Node"() {
-        transformer.indentation = "\t"
-        transformer.addAction { XmlProvider provider -> provider.asNode().children()[0].appendNode("grandchild") }
-
-        when:
-        def result = transformer.transform("<root>\n  <child/>\n</root>\n")
-
-        then:
-        looksLike "<root>\n\t<child>\n\t\t<grandchild/>\n\t</child>\n</root>\n", result
-    }
-
-    def "can add DOCTYPE along with nodes"() {
-        transformer.addAction { it.asNode().appendNode('someChild') }
-        transformer.addAction {
-            def s = it.asString()
-            s.insert(s.indexOf("?>") + 2, '\n<!DOCTYPE application PUBLIC "-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN" "http://java.sun.com/dtd/application_1_3.dtd">')
-        }
-
-        when:
-        def result = transformer.transform("<root></root>")
-
-        then:
-        looksLike "<!DOCTYPE application PUBLIC \"-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN\" \"http://java.sun.com/dtd/application_1_3.dtd\">\n<root>\n  <someChild/>\n</root>\n", result
-    }
-
-    def "can specify DOCTYPE when using DomNode"() {
-        StringWriter writer = new StringWriter()
-        def node = new DomNode('root')
-        node.publicId = 'public-id'
-        node.systemId = 'system-id'
-
-        when:
-        transformer.transform(node, writer)
-
-        then:
-        looksLike '''<!DOCTYPE root PUBLIC "public-id" "system-id">
-<root/>
-''', writer.toString()
-    }
-
-    def "DOCTYPE is preserved when transformed as a Node"() {
-        StringWriter writer = new StringWriter()
-        def node = new DomNode('root')
-        node.publicId = 'public-id'
-        node.systemId = 'system-id'
-        transformer.addAction { it.asNode().appendNode('someChild') }
-
-        when:
-        transformer.transform(node, writer)
-
-        then:
-        looksLike '''<!DOCTYPE root PUBLIC "public-id" "system-id">
-<root>
-  <someChild/>
-</root>
-''', writer.toString()
-    }
-
-    def "DOCTYPE is preserved when transformed as a DOM element"() {
-        StringWriter writer = new StringWriter()
-        def node = new DomNode('root')
-        node.publicId = 'public-id'
-        node.systemId = getClass().getResource("xml-transformer-test.dtd")
-        transformer.addAction { it.asElement().appendChild(it.asElement().ownerDocument.createElement('someChild')) }
-
-        when:
-        transformer.transform(node, writer)
-
-        then:
-        looksLike """<!DOCTYPE root PUBLIC "public-id" "${node.getSystemId()}">
-<root>
-  <someChild/>
-</root>
-""", writer.toString()
-    }
-
-    def "indentation correct when writing out DOM element (only) if indenting with spaces"() {
-        transformer.indentation = expected
-        transformer.addAction { XmlProvider provider ->
-            def document = provider.asElement().ownerDocument
-            document.getElementsByTagName("child").item(0).appendChild(document.createElement("grandchild"))
-        }
-
-        when:
-        def result = transformer.transform("<root>\n  <child/>\n</root>\n")
-
-        then:
-        looksLike("<root>\n$actual<child>\n$actual$actual<grandchild/>\n$actual</child>\n</root>\n", result)
-
-        where:
-        expected | actual
-        "    "   | "    "
-        "\t"     | "  " // tabs not supported, two spaces used instead
-    }
-
-    def "can use with action api"() {
-        given:
-        def writer = new StringWriter()
-        def input = "<things><thing/></things>"
-        def generator = new Action<Writer>() {
-            void execute(Writer t) {
-                t.write(input)
-            }
-        }
-
-        when:
-        transformer.transform(writer, generator)
-
-        then:
-        looksLike(input, writer.toString())
-
-        when:
-        writer.buffer.setLength(0)
-        transformer.addAction(new Action<XmlProvider>() {
-            void execute(XmlProvider xml) {
-                xml.asNode().thing[0]. at foo = "bar"
-            }
-        })
-        transformer.transform(writer, generator)
-
-        then:
-        looksLike('<things>\n  <thing foo="bar"/>\n</things>', writer.toString())
-    }
-
-    private void looksLike(String expected, String actual) {
-        assert removeTrailingWhitespace(actual) == removeTrailingWhitespace(TextUtil.toPlatformLineSeparators(addXmlDeclaration(expected)))
-    }
-
-    private String removeTrailingWhitespace(String value) {
-        return value.replaceFirst('(?s)\\s+$', "")
-    }
-
-    private String addXmlDeclaration(String value) {
-        "<?xml version=\"1.0\"?>\n" + value
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainerTest.groovy
index 0ba1105..4e41756 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainerTest.groovy
@@ -29,34 +29,34 @@ import spock.lang.Specification
 
 class DefaultArtifactRepositoryContainerTest extends Specification {
 
-    BaseRepositoryFactory baseRepositoryFactory
+    BaseRepositoryFactory repositoryFactory
     DefaultArtifactRepositoryContainer container
 
     def setup() {
-        baseRepositoryFactory = Mock(BaseRepositoryFactory)
+        repositoryFactory = Mock(BaseRepositoryFactory)
         container = createResolverContainer()
     }
 
     ArtifactRepositoryContainer createResolverContainer(
-            BaseRepositoryFactory baseRepositoryFactory = baseRepositoryFactory,
+            BaseRepositoryFactory repositoryFactory = repositoryFactory,
             Instantiator instantiator = new DirectInstantiator()
     ) {
-        new DefaultArtifactRepositoryContainer(baseRepositoryFactory, instantiator)
+        new DefaultArtifactRepositoryContainer(repositoryFactory, instantiator)
     }
 
-    List setupNotation(int i, baseRepositoryFactory = baseRepositoryFactory) {
-        setupNotation("repoNotation$i", "repo$i", "resolver$i", baseRepositoryFactory)
+    List setupNotation(int i, repositoryFactory = repositoryFactory) {
+        setupNotation("repoNotation$i", "repo$i", "resolver$i", repositoryFactory)
     }
 
-    List setupNotation(notation, repoName, resolverName, baseRepositoryFactory = baseRepositoryFactory) {
+    List setupNotation(notation, repoName, resolverName, repositoryFactory = repositoryFactory) {
         def repo = Mock(ArtifactRepositoryInternal) { getName() >> repoName }
         def resolver = new FileSystemResolver()
         def resolverRepo = Spy(FixedResolverArtifactRepository, constructorArgs: [resolver])
 
         interaction {
-            1 * baseRepositoryFactory.createRepository(notation) >> repo
-            1 * baseRepositoryFactory.toResolver(repo) >> resolver
-            1 * baseRepositoryFactory.createResolverBackedRepository(resolver) >> resolverRepo
+            1 * repositoryFactory.createRepository(notation) >> repo
+            1 * repositoryFactory.toResolver(repo) >> resolver
+            1 * repositoryFactory.createResolverBackedRepository(resolver) >> resolverRepo
             1 * resolverRepo.onAddToContainer(container)
         }
 
@@ -95,9 +95,9 @@ class DefaultArtifactRepositoryContainerTest extends Specification {
         def resolverRepo = Spy(FixedResolverArtifactRepository, constructorArgs: [resolver])
 
         interaction {
-            1 * baseRepositoryFactory.createRepository(resolver) >> repo
-            1 * baseRepositoryFactory.toResolver(repo) >> resolver
-            1 * baseRepositoryFactory.createResolverBackedRepository(resolver) >> resolverRepo
+            1 * repositoryFactory.createRepository(resolver) >> repo
+            1 * repositoryFactory.toResolver(repo) >> resolver
+            1 * repositoryFactory.createResolverBackedRepository(resolver) >> resolverRepo
             1 * resolverRepo.onAddToContainer(container)
         }
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifierTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifierTest.groovy
deleted file mode 100755
index 00a327c..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifierTest.groovy
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts
-
-import spock.lang.Specification
-import static org.gradle.util.Matchers.strictlyEqual
-
-class DefaultModuleVersionIdentifierTest extends Specification {
-    def "has useful toString()"() {
-        def module = new DefaultModuleVersionIdentifier("group", "module", "version")
-
-        expect:
-        module.toString().contains("group: group, module: module, version: version")
-    }
-
-    def "ids are equal when group, module and version are equal"() {
-        def module = new DefaultModuleVersionIdentifier("group", "module", "version")
-        def same = new DefaultModuleVersionIdentifier("group", "module", "version")
-        def differentGroup = new DefaultModuleVersionIdentifier("other", "module", "version")
-        def differentModule = new DefaultModuleVersionIdentifier("group", "other", "version")
-        def differentVersion = new DefaultModuleVersionIdentifier("group", "module", "other")
-
-        expect:
-        module strictlyEqual(same)
-        module != differentGroup
-        module != differentModule
-        module != differentVersion
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ProjectBackedModuleTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ProjectBackedModuleTest.groovy
new file mode 100644
index 0000000..89a8e3e
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ProjectBackedModuleTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts
+
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class ProjectBackedModuleTest extends Specification {
+
+    def "module exposes project properties"() {
+        given:
+        def project = HelperUtil.createRootProject()
+        def module = new ProjectBackedModule(project)
+
+        expect:
+        module.name == project.name
+        module.group == project.group.toString()
+        module.version == project.version.toString()
+        module.status == project.status.toString()
+
+        when:
+        project.group = "fo${1}o"
+        project.version = "fo${2}o"
+        project.status = "fo${3}o"
+
+        then:
+        module.group == "fo1o"
+        module.version == "fo2o"
+        module.status == "fo3o"
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierTest.groovy
deleted file mode 100644
index 9b12887..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierTest.groovy
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package org.gradle.api.internal.artifacts
-
-import spock.lang.Specification
-import org.gradle.util.Matchers
-
-class ResolvedConfigurationIdentifierTest extends Specification {
-    def equalsAndHashCode() {
-        when:
-        ResolvedConfigurationIdentifier id = new ResolvedConfigurationIdentifier('group', 'name', 'version', 'config')
-        ResolvedConfigurationIdentifier same = new ResolvedConfigurationIdentifier('group', 'name', 'version', 'config')
-        ResolvedConfigurationIdentifier differentGroup = new ResolvedConfigurationIdentifier('other', 'name', 'version', 'config')
-        ResolvedConfigurationIdentifier differentName = new ResolvedConfigurationIdentifier('group', 'other', 'version', 'config')
-        ResolvedConfigurationIdentifier differentVersion = new ResolvedConfigurationIdentifier('group', 'name', 'other', 'config')
-        ResolvedConfigurationIdentifier differentConfig = new ResolvedConfigurationIdentifier('group', 'name', 'version', 'other')
-
-        then:
-        Matchers.strictlyEquals(id, same)
-        id != differentGroup
-        id != differentName
-        id != differentVersion
-        id != differentConfig
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsTest.java
deleted file mode 100644
index bdd434e..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsTest.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.configurations;
-
-import org.hamcrest.Matchers;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-
-/**
- * @author Hans Dockter
- */
-public class ConfigurationsTest {
-    private static final String TEST_CONF = "testConf";
-
-    @Test
-    public void testUploadInternalTaskName() {
-        assertThat(Configurations.uploadInternalTaskName(TEST_CONF), Matchers.equalTo("uploadTestConfInternal"));
-    }
-
-    @Test
-    public void testUploadTaskName() {
-        assertThat(Configurations.uploadTaskName(TEST_CONF), Matchers.equalTo("uploadTestConf"));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy
deleted file mode 100644
index ee17fdc..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.configurations;
-
-
-import org.gradle.api.artifacts.UnknownConfigurationException
-import org.gradle.api.internal.DomainObjectContext
-import org.gradle.internal.reflect.Instantiator
-import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
-import org.gradle.listener.ListenerManager
-import org.gradle.util.HelperUtil
-import spock.lang.Specification
-
-/**
- * @author Hans Dockter, Szczepan
- */
-public class DefaultConfigurationContainerSpec extends Specification {
-    private static final String TEST_DESCRIPTION = "testDescription";
-    private static final Closure TEST_CLOSURE = HelperUtil.createSetterClosure("Description", TEST_DESCRIPTION);
-    private static final String TEST_NAME = "testName";
-
-    private ArtifactDependencyResolver dependencyResolver = Mock()
-    private Instantiator instantiator = Mock()
-    private DomainObjectContext domainObjectContext = Mock()
-    private ListenerManager listenerManager = Mock()
-    private DependencyMetaDataProvider metaDataProvider = Mock()
-    def ConfigurationInternal conf = Mock()
-
-    private DefaultConfigurationContainer configurationContainer = new DefaultConfigurationContainer(dependencyResolver, instantiator, domainObjectContext, listenerManager, metaDataProvider);
-
-    def "adds and gets"() {
-        _ * conf.getName() >> "compile"
-        1 * domainObjectContext.absoluteProjectPath("compile") >> ":compile"
-        1 * instantiator.newInstance(DefaultConfiguration.class, ":compile", "compile", configurationContainer,
-                dependencyResolver, listenerManager, metaDataProvider, _ as DefaultResolutionStrategy) >> conf
-
-        when:
-        def compile = configurationContainer.add("compile")
-
-        then:
-        configurationContainer.getByName("compile") == compile
-
-        when:
-        configurationContainer.getByName("fooo")
-
-        then:
-        thrown(UnknownConfigurationException)
-    }
-
-    def "configures and finds"() {
-        _ * conf.getName() >> "compile"
-        1 * domainObjectContext.absoluteProjectPath("compile") >> ":compile"
-        1 * instantiator.newInstance(DefaultConfiguration.class, ":compile", "compile", configurationContainer,
-                dependencyResolver, listenerManager, metaDataProvider, _ as DefaultResolutionStrategy) >> conf
-
-        when:
-        def compile = configurationContainer.add("compile") {
-            description = "I compile!"
-        }
-
-        then:
-        configurationContainer.getByName("compile") == compile
-        1 * conf.setDescription("I compile!")
-
-        //finds configurations
-        configurationContainer.findByName("compile") == compile
-        configurationContainer.findByName("fooo") == null
-        configurationContainer.findAll { it.name == "compile" } as Set == [compile] as Set
-        configurationContainer.findAll { it.name == "fooo" } as Set == [] as Set
-
-        configurationContainer as List == [compile] as List
-    }
-
-    def "creates detached"() {
-        given:
-        def dependency1 = HelperUtil.createDependency("group1", "name1", "version1");
-        def dependency2 = HelperUtil.createDependency("group2", "name2", "version2");
-
-        when:
-        def detached = configurationContainer.detachedConfiguration(dependency1, dependency2);
-
-        then:
-        detached.getAll() == [detached] as Set
-        detached.getHierarchy() == [detached] as Set
-        [dependency1, dependency2].each { detached.getDependencies().contains(it) }
-        detached.getDependencies().size() == 2
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy
deleted file mode 100644
index 8a3b896..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.configurations
-
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.UnknownConfigurationException
-import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
-import org.gradle.listener.ListenerManager
-import org.gradle.util.JUnit4GroovyMockery
-import org.jmock.integration.junit4.JMock
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.gradle.api.internal.*
-import static org.junit.Assert.*
-import static org.hamcrest.Matchers.*
-import org.gradle.internal.reflect.Instantiator
-import org.gradle.internal.reflect.DirectInstantiator
-
-/**
- * @author Hans Dockter
- */
-
- at RunWith(JMock)
-class DefaultConfigurationContainerTest {
-    private JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-
-    private ArtifactDependencyResolver dependencyResolver = context.mock(ArtifactDependencyResolver)
-    private DomainObjectContext domainObjectContext = context.mock(DomainObjectContext.class)
-    private ListenerManager listenerManager = context.mock(ListenerManager.class)
-    private DependencyMetaDataProvider metaDataProvider = context.mock(DependencyMetaDataProvider.class)
-    private Instantiator instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator())
-    private DefaultConfigurationContainer configurationHandler = instantiator.newInstance(DefaultConfigurationContainer.class,
-            dependencyResolver, instantiator, { name -> name } as DomainObjectContext,
-            listenerManager, metaDataProvider)
-
-    @Before
-    public void setup() {
-        context.checking {
-            ignoring(listenerManager)
-        }
-    }
-
-    @Test
-    void addsNewConfigurationWhenConfiguringSelf() {
-        configurationHandler.configure {
-            newConf
-        }
-        assertThat(configurationHandler.findByName('newConf'), notNullValue())
-        assertThat(configurationHandler.newConf, notNullValue())
-    }
-
-    @Test(expected = UnknownConfigurationException)
-    void doesNotAddNewConfigurationWhenNotConfiguringSelf() {
-        configurationHandler.getByName('unknown')
-    }
-
-    @Test
-    void makesExistingConfigurationAvailableAsProperty() {
-        Configuration configuration = configurationHandler.add('newConf')
-        assertThat(configuration, notNullValue())
-        assertThat(configurationHandler.getByName("newConf"), sameInstance(configuration))
-        assertThat(configurationHandler.newConf, sameInstance(configuration))
-    }
-
-    @Test
-    void addsNewConfigurationWithClosureWhenConfiguringSelf() {
-        String someDesc = 'desc1'
-        configurationHandler.configure {
-            newConf {
-                description = someDesc
-            }
-        }
-        assertThat(configurationHandler.newConf.getDescription(), equalTo(someDesc))
-    }
-
-    @Test
-    void makesExistingConfigurationAvailableAsConfigureMethod() {
-        String someDesc = 'desc1'
-        configurationHandler.add('newConf')
-        Configuration configuration = configurationHandler.newConf {
-            description = someDesc
-        }
-        assertThat(configuration.getDescription(), equalTo(someDesc))
-    }
-
-    @Test
-    void makesExistingConfigurationAvailableAsConfigureMethodWhenConfiguringSelf() {
-        String someDesc = 'desc1'
-        Configuration configuration = configurationHandler.add('newConf')
-        configurationHandler.configure {
-            newConf {
-                description = someDesc
-            }
-        }
-        assertThat(configuration.getDescription(), equalTo(someDesc))
-    }
-
-    @Test(expected = MissingMethodException)
-    void newConfigurationWithNonClosureParametersShouldThrowMissingMethodEx() {
-        configurationHandler.newConf('a', 'b')
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy
deleted file mode 100644
index ced0cab..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.configurations
-
-import org.gradle.api.Action
-import org.gradle.api.Task
-import org.gradle.api.artifacts.result.ResolutionResult
-import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
-import org.gradle.api.internal.artifacts.ResolverResults
-import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
-import org.gradle.api.tasks.TaskDependency
-import org.gradle.listener.ListenerBroadcast
-import org.gradle.listener.ListenerManager
-import spock.lang.Specification
-import org.gradle.api.artifacts.*
-
-class DefaultConfigurationSpec extends Specification {
-
-    ConfigurationsProvider configurationsProvider = Mock()
-    ArtifactDependencyResolver dependencyResolver = Mock()
-    ListenerManager listenerManager = Mock()
-    DependencyMetaDataProvider metaDataProvider = Mock()
-    DefaultResolutionStrategy defaultResolutionStrategy = Mock()
-
-    DefaultConfiguration conf(String confName = "conf", String path = ":conf") {
-        new DefaultConfiguration(path, confName, configurationsProvider, dependencyResolver, listenerManager, metaDataProvider, defaultResolutionStrategy)
-    }
-
-    DefaultPublishArtifact artifact(String name) {
-        artifact(name: name)
-    }
-
-    DefaultPublishArtifact artifact(Map props = [:]) {
-        new DefaultPublishArtifact(
-            props.name ?: "artifact",
-            props.extension ?: "artifact",
-            props.type,
-            props.classifier,
-            props.date,
-            props.file,
-            props.tasks ?: []
-        )
-    }
-
-    // You need to wrap this in an interaction {} block when calling it
-    ResolvedConfiguration resolvedConfiguration(Configuration config, ArtifactDependencyResolver dependencyResolver = dependencyResolver) {
-        ResolvedConfiguration resolvedConfiguration = Mock()
-        1 * dependencyResolver.resolve(config) >> new ResolverResults(resolvedConfiguration, Mock(ResolutionResult))
-        resolvedConfiguration
-    }
-
-    def setup() {
-        ListenerBroadcast<DependencyResolutionListener> broadcast = new ListenerBroadcast<DependencyResolutionListener>(DependencyResolutionListener)
-        _ * listenerManager.createAnonymousBroadcaster(DependencyResolutionListener) >> broadcast
-    }
-
-    def "all artifacts collection has immediate artifacts"() {
-        given:
-        def c = conf()
-
-        when:
-        c.artifacts << artifact()
-        c.artifacts << artifact()
-
-        then:
-        c.allArtifacts.size() == 2
-    }
-
-    def "all artifacts collection has inherited artifacts"() {
-        given:
-        def master = conf()
-
-        def masterParent1 = conf()
-        def masterParent2 = conf()
-        master.extendsFrom masterParent1, masterParent2
-
-        def masterParent1Parent1 = conf()
-        def masterParent1Parent2 = conf()
-        masterParent1.extendsFrom masterParent1Parent1, masterParent1Parent2
-
-        def masterParent2Parent1 = conf()
-        def masterParent2Parent2 = conf()
-        masterParent2.extendsFrom masterParent2Parent1, masterParent2Parent2
-
-        def allArtifacts = master.allArtifacts
-
-        def added = []
-        allArtifacts.whenObjectAdded { added << it.name }
-        def removed = []
-        allArtifacts.whenObjectRemoved { removed << it.name }
-
-        expect:
-        allArtifacts.empty
-
-        when:
-        masterParent1.artifacts << artifact("p1-1")
-        masterParent1Parent1.artifacts << artifact("p1p1-1")
-        masterParent1Parent2.artifacts << artifact("p1p2-1")
-        masterParent2.artifacts << artifact("p2-1")
-        masterParent2Parent1.artifacts << artifact("p2p1-1")
-        masterParent2Parent2.artifacts << artifact("p2p2-1")
-
-        then:
-        allArtifacts.size() == 6
-        added == ["p1-1", "p1p1-1", "p1p2-1", "p2-1", "p2p1-1", "p2p2-1"]
-
-        when:
-        masterParent2Parent2.artifacts.remove masterParent2Parent2.artifacts.toList().first()
-
-        then:
-        allArtifacts.size() == 5
-        removed == ["p2p2-1"]
-
-        when:
-        removed.clear()
-        masterParent1.extendsFrom = []
-
-        then:
-        allArtifacts.size() == 3
-        removed == ["p1p1-1", "p1p2-1"]
-    }
-
-    def "incoming dependencies set has same name and path as owner configuration"() {
-        def config = conf("conf", ":path")
-
-        expect:
-        config.incoming.name == "conf"
-        config.incoming.path == ":path"
-    }
-
-    def "incoming dependencies set contains immediate dependencies"() {
-        def config = conf("conf")
-        Dependency dep1 = Mock()
-
-        given:
-        config.dependencies.add(dep1)
-
-        expect:
-        config.incoming.dependencies as List == [dep1]
-    }
-
-    def "incoming dependencies set contains inherited dependencies"() {
-        def parent = conf("conf")
-        def config = conf("conf")
-        Dependency dep1 = Mock()
-
-        given:
-        config.extendsFrom parent
-        parent.dependencies.add(dep1)
-
-        expect:
-        config.incoming.dependencies as List == [dep1]
-    }
-
-    def "incoming dependencies set files are resolved lazily"() {
-        setup:
-        def config = conf("conf")
-
-        when:
-        def files = config.incoming.files
-
-        then:
-        0 * _._
-
-        when:
-        files.files
-
-        then:
-        interaction { resolvedConfiguration(config) }
-        0 * dependencyResolver._
-    }
-
-    def "incoming dependencies set depends on all self resolving dependencies"() {
-        SelfResolvingDependency dependency = Mock()
-        Task task = Mock()
-        TaskDependency taskDep = Mock()
-        def config = conf("conf")
-
-        given:
-        config.dependencies.add(dependency)
-
-        when:
-        def depTaskDeps = config.incoming.dependencies.buildDependencies.getDependencies(null)
-        def fileTaskDeps = config.incoming.files.buildDependencies.getDependencies(null)
-
-        then:
-        depTaskDeps == [task] as Set
-        fileTaskDeps == [task] as Set
-        _ * dependency.buildDependencies >> taskDep
-        _ * taskDep.getDependencies(_) >> ([task] as Set)
-        0 * _._
-    }
-
-    def "notifies beforeResolve action on incoming dependencies set when dependencies are resolved"() {
-        Action<ResolvableDependencies> action = Mock()
-        def config = conf("conf")
-
-        given:
-        config.incoming.beforeResolve(action)
-
-        when:
-        config.resolvedConfiguration
-
-        then:
-        interaction { resolvedConfiguration(config) }
-        1 * action.execute(config.incoming)
-    }
-
-    def "calls beforeResolve closure on incoming dependencies set when dependencies are resolved"() {
-        def config = conf("conf")
-        resolvedConfiguration(config)
-        def called = false
-
-        expect:
-        config.incoming.afterResolve {
-            assert it == config.incoming
-            called = true
-        }
-
-        when:
-        config.resolvedConfiguration
-
-        then:
-        called
-    }
-
-    def "notifies afterResolve action on incoming dependencies set when dependencies are resolved"() {
-        Action<ResolvableDependencies> action = Mock()
-        def config = conf("conf")
-
-        given:
-        config.incoming.afterResolve(action)
-
-        when:
-        config.resolvedConfiguration
-
-        then:
-        interaction { resolvedConfiguration(config) }
-        1 * action.execute(config.incoming)
-
-    }
-
-    def "calls afterResolve closure on incoming dependencies set when dependencies are resolved"() {
-        def config = conf("conf")
-        resolvedConfiguration(config)
-        def called = false
-
-        expect:
-        config.incoming.afterResolve {
-            assert it == config.incoming
-            called = true
-        }
-
-        when:
-        config.resolvedConfiguration
-
-        then:
-        called
-    }
-    
-    def "a recursive copy of a configuration includes inherited exclude rules"() {
-        given:
-        def (p1, p2, child) = [conf("p1"), conf("p2"), conf("child")]
-        child.extendsFrom p1, p2
-        
-        and:
-        def (p1Exclude, p2Exclude) = [[group: 'p1', module: 'p1'], [group: 'p2', module: 'p2']]
-        p1.exclude p1Exclude
-        p2.exclude p2Exclude
-        
-        when:
-        def copied = child.copyRecursive()
-        
-        then:
-        copied.excludeRules.size() == 2
-        copied.excludeRules.collect{[group: it.group, module: it.module]}.sort { it.group } == [p1Exclude, p2Exclude]
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java
deleted file mode 100644
index 21d5307..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java
+++ /dev/null
@@ -1,954 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.configurations;
-
-import groovy.lang.Closure;
-import org.gradle.api.GradleException;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.artifacts.*;
-import org.gradle.api.artifacts.result.ResolutionResult;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.artifacts.*;
-import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.specs.Specs;
-import org.gradle.api.tasks.TaskContainer;
-import org.gradle.api.tasks.TaskDependency;
-import org.gradle.listener.ListenerBroadcast;
-import org.gradle.listener.ListenerManager;
-import org.gradle.util.HelperUtil;
-import org.gradle.util.JUnit4GroovyMockery;
-import org.gradle.util.TestClosure;
-import org.gradle.util.WrapUtil;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.util.*;
-
-import static org.gradle.util.Matchers.isEmpty;
-import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
- at RunWith(JMock.class)
-public class DefaultConfigurationTest {
-    private JUnit4Mockery context = new JUnit4GroovyMockery();
-    private ArtifactDependencyResolver dependencyResolver = context.mock(ArtifactDependencyResolver.class);
-    private ConfigurationsProvider configurationContainer;
-    private ListenerManager listenerManager = context.mock(ListenerManager.class);
-    private DependencyMetaDataProvider metaDataProvider = context.mock(DependencyMetaDataProvider.class);
-    private DefaultResolutionStrategy resolutionStrategy = new DefaultResolutionStrategy();
-    private DefaultConfiguration configuration;
-    private DependencyResolutionListener dependencyResolutionBroadcast = context.mock(DependencyResolutionListener.class);
-    private ListenerBroadcast resolutionListenerBroadcast = context.mock(ListenerBroadcast.class); 
-
-    @Before
-    public void setUp() {
-        configurationContainer = context.mock(ConfigurationsProvider.class);
-        context.checking(new Expectations(){{
-            allowing(listenerManager).createAnonymousBroadcaster(DependencyResolutionListener.class);
-            will(returnValue(resolutionListenerBroadcast));
-            allowing(resolutionListenerBroadcast).getSource();
-            will(returnValue(dependencyResolutionBroadcast));
-            allowing(dependencyResolutionBroadcast).afterResolve(with(any(ResolvableDependencies.class)));
-            allowing(dependencyResolutionBroadcast).beforeResolve(with(any(ResolvableDependencies.class)));
-        }});
-        configuration = createNamedConfiguration("path", "name");
-    }
-
-    @Test
-    public void defaultValues() {
-        assertThat(configuration.getName(), equalTo("name"));
-        assertThat(configuration.isVisible(), equalTo(true));
-        assertThat(configuration.getExtendsFrom().size(), equalTo(0));
-        assertThat(configuration.isTransitive(), equalTo(true));
-        assertThat(configuration.getDescription(), nullValue());
-        assertThat(configuration.getState(), equalTo(Configuration.State.UNRESOLVED));
-        assertThat(configuration.getDisplayName(), equalTo("configuration 'path'"));
-    }
-
-    @Test
-    public void hasUsefulDisplayName() {
-        assertThat(configuration.getDisplayName(), equalTo("configuration 'path'"));
-        assertThat(configuration.toString(), equalTo("configuration 'path'"));
-        assertThat(configuration.getIncoming().toString(), equalTo("dependencies 'path'"));
-    }
-
-    @Test
-    public void withPrivateVisibility() {
-        configuration.setVisible(false);
-        assertFalse(configuration.isVisible());
-    }
-
-    @Test
-    public void withIntransitive() {
-        configuration.setTransitive(false);
-        assertFalse(configuration.isTransitive());
-    }
-
-    @Test
-    public void exclude() {
-        Map<String, String> excludeArgs1 = toMap("group", "aGroup");
-        Map<String, String> excludeArgs2 = toMap("module", "aModule");
-        assertThat(configuration.exclude(excludeArgs1), sameInstance(configuration));
-        configuration.exclude(excludeArgs2);
-        assertThat(configuration.getExcludeRules(), equalTo(WrapUtil.<ExcludeRule>toSet(
-                new DefaultExcludeRule("aGroup", null), new DefaultExcludeRule(null, "aModule"))));
-    }
-
-    @Test
-    public void setExclude() {
-        Set<ExcludeRule> excludeRules = WrapUtil.<ExcludeRule>toSet(new DefaultExcludeRule("groupValue", null));
-        configuration.setExcludeRules(excludeRules);
-        assertThat(configuration.getExcludeRules(), equalTo(excludeRules));
-    }
-
-    @Test
-    public void withDescription() {
-        configuration.setDescription("description");
-        assertThat(configuration.getDescription(), equalTo("description"));
-    }
-
-    @Test
-    public void extendsOtherConfigurations() {
-        Configuration configuration1 = createNamedConfiguration("otherConf1");
-        configuration.extendsFrom(configuration1);
-        assertThat(configuration.getExtendsFrom(), equalTo(toSet(configuration1)));
-
-        Configuration configuration2 = createNamedConfiguration("otherConf2");
-        configuration.extendsFrom(configuration2);
-        assertThat(configuration.getExtendsFrom(), equalTo(toSet(configuration1, configuration2)));
-    }
-
-    @Test
-    public void setExtendsFrom() {
-        Configuration configuration1 = createNamedConfiguration("otherConf1");
-
-        configuration.setExtendsFrom(toSet(configuration1));
-        assertThat(configuration.getExtendsFrom(), equalTo(toSet(configuration1)));
-
-        Configuration configuration2 = createNamedConfiguration("otherConf2");
-        configuration.setExtendsFrom(toSet(configuration2));
-        assertThat(configuration.getExtendsFrom(), equalTo(toSet(configuration2)));
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void extendsFromWithDirectCycleShouldThrowInvalidUserDataEx() {
-        Configuration otherConfiguration = createNamedConfiguration("otherConf");
-        otherConfiguration.extendsFrom(configuration);
-        configuration.extendsFrom(otherConfiguration);
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void extendsFromWithIndirectCycleShouldThrowInvalidUserDataEx() {
-        Configuration otherConfiguration1 = createNamedConfiguration("otherConf1");
-        Configuration otherConfiguration2 = createNamedConfiguration("otherConf2");
-        configuration.extendsFrom(otherConfiguration1);
-        otherConfiguration1.extendsFrom(otherConfiguration2);
-        otherConfiguration2.extendsFrom(configuration);
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void setExtendsFromWithCycleShouldThrowInvalidUserDataEx() {
-        Configuration otherConfiguration = createNamedConfiguration("otherConf");
-        otherConfiguration.extendsFrom(configuration);
-        configuration.setExtendsFrom(toSet(otherConfiguration));
-    }
-
-    @Test
-    public void getHierarchy() {
-        Configuration root1 = createNamedConfiguration("root1");
-        Configuration middle1 = createNamedConfiguration("middle1").extendsFrom(root1);
-        Configuration root2 = createNamedConfiguration("root2");
-        Configuration middle2 = createNamedConfiguration("middle2").extendsFrom(root1, root2);
-        createNamedConfiguration("root3");
-        Configuration leaf = createNamedConfiguration("leaf1").extendsFrom(middle1, middle2);
-        Set<Configuration> hierarchy = leaf.getHierarchy();
-        assertThat(hierarchy.size(), equalTo(5));
-        assertThat(hierarchy.iterator().next(), equalTo(leaf));
-        assertBothExistsAndOneIsBeforeOther(hierarchy, middle1, root1);
-        assertBothExistsAndOneIsBeforeOther(hierarchy, middle2, root2);
-    }
-
-    private void assertBothExistsAndOneIsBeforeOther(Set<Configuration> hierarchy, Configuration beforeConf, Configuration afterConf) {
-        assertThat(hierarchy, hasItem(beforeConf));
-        assertThat(hierarchy, hasItem(afterConf));
-
-        boolean foundBeforeConf = false;
-        for (Configuration configuration : hierarchy) {
-            if (configuration.equals(beforeConf)) {
-                foundBeforeConf = true;
-            }
-            if (configuration.equals(afterConf)) {
-                assertThat(foundBeforeConf, equalTo(true));
-            }
-        }
-    }
-
-    @Test
-    public void getAll() {
-        final Configuration conf1 = createNamedConfiguration("testConf1");
-        final Configuration conf2 = createNamedConfiguration("testConf2");
-        context.checking(new Expectations(){{
-            one(configurationContainer).getAll();
-            will(returnValue(toSet(conf1, conf2)));
-        }});
-        assertThat(configuration.getAll(), equalTo(toSet(conf1, conf2)));
-    }
-
-    @Test(expected = GradleException.class)
-    public void getAsPathShouldRethrownFailure() {
-        prepareForResolveWithErrors();
-        configuration.resolve();
-    }
-
-    @Test
-    public void resolve() {
-        final Set<File> fileSet = toSet(new File("somePath"));
-        makeResolveReturnFileSet(fileSet);
-        assertThat(configuration.resolve(), equalTo(fileSet));
-        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
-    }
-
-    @Test
-    public void filesWithDependencies() {
-        final Set<File> fileSet = toSet(new File("somePath"));
-        prepareForFilesBySpec(fileSet);
-        assertThat(configuration.files(context.mock(Dependency.class)), equalTo(fileSet));
-        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
-    }
-
-    @Test
-    public void fileCollectionWithDependencies() {
-        Dependency dependency1 = HelperUtil.createDependency("group1", "name", "version");
-        Dependency dependency2 = HelperUtil.createDependency("group2", "name", "version");
-        DefaultConfiguration.ConfigurationFileCollection fileCollection = (DefaultConfiguration.ConfigurationFileCollection)
-                configuration.fileCollection(dependency1);
-        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(dependency1),
-                equalTo(true));
-        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(dependency2),
-                equalTo(false));
-    }
-
-    @SuppressWarnings("unchecked")
-    @Test
-    public void filesWithSpec() {
-        final Set<File> fileSet = toSet(new File("somePath"));
-        prepareForFilesBySpec(fileSet);
-        assertThat(configuration.files(context.mock(Spec.class)), equalTo(fileSet));
-        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
-    }
-
-
-    @Test
-    public void fileCollectionWithSpec() {
-        @SuppressWarnings("unchecked")
-        Spec<Dependency> spec = context.mock(Spec.class);
-        DefaultConfiguration.ConfigurationFileCollection fileCollection = (DefaultConfiguration.ConfigurationFileCollection)
-                configuration.fileCollection(spec);
-        assertThat(fileCollection.getDependencySpec(), sameInstance((Object)spec));
-    }
-
-    @Test
-    public void filesWithClosureSpec() {
-        Closure closure = HelperUtil.toClosure("{ dep -> dep.group == 'group1' }");
-        final Set<File> fileSet = toSet(new File("somePath"));
-        prepareForFilesBySpec(fileSet);
-        assertThat(configuration.files(closure), equalTo(fileSet));
-        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
-    }
-
-    @Test
-    public void fileCollectionWithClosureSpec() {
-        Closure closure = HelperUtil.toClosure("{ dep -> dep.group == 'group1' }");
-        DefaultConfiguration.ConfigurationFileCollection fileCollection = (DefaultConfiguration.ConfigurationFileCollection)
-                configuration.fileCollection(closure);
-        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(HelperUtil.createDependency("group1", "name", "version")),
-                equalTo(true));
-        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(HelperUtil.createDependency("group2", "name", "version")),
-                equalTo(false));
-    }
-
-    @SuppressWarnings("unchecked")
-    private void prepareForFilesBySpec(final Set<File> fileSet) {
-        final ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
-        prepareResolve(resolvedConfiguration, false);
-        context.checking(new Expectations() {{
-            one(resolvedConfiguration).getFiles(with(any(Spec.class)));
-            will(returnValue(fileSet));
-        }});
-    }
-
-    @Test(expected = GradleException.class)
-    public void resolveShouldRethrowFailure() {
-        prepareForResolveWithErrors();
-        configuration.resolve();
-    }
-
-    private void prepareForResolveWithErrors() {
-        final ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
-        prepareResolve(resolvedConfiguration, true);
-        context.checking(new Expectations(){{
-            one(resolvedConfiguration).rethrowFailure();
-            will(throwException(new GradleException()));
-        }});
-    }
-
-    private void makeResolveReturnFileSet(final Set<File> fileSet) {
-        final ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
-        context.checking(new Expectations() {{
-            prepareResolve(resolvedConfiguration, false);
-            allowing(resolvedConfiguration).getFiles(Specs.SATISFIES_ALL);
-            will(returnValue(fileSet));
-        }});
-    }
-
-    @Test
-    public void resolveSuccessfullyAsResolvedConfiguration() {
-        ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
-        prepareResolve(resolvedConfiguration, false);
-        assertThat(configuration.getResolvedConfiguration(), equalTo(resolvedConfiguration));
-        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
-    }
-
-    private void prepareResolve(final ResolvedConfiguration resolvedConfiguration, final boolean withErrors) {
-        context.checking(new Expectations() {{
-            allowing(dependencyResolver).resolve(configuration);
-            will(returnValue(new ResolverResults(resolvedConfiguration, context.mock(ResolutionResult.class))));
-            allowing(resolvedConfiguration).hasError();
-            will(returnValue(withErrors));
-        }});
-    }
-
-    @Test
-    public void multipleResolvesShouldUseCachedResult() {
-        prepareResolve(context.mock(ResolvedConfiguration.class), true);
-        assertThat(configuration.getResolvedConfiguration(), sameInstance(configuration.getResolvedConfiguration()));
-    }
-
-    @Test
-    public void uploadTaskName() {
-        assertThat(configuration.getUploadTaskName(), equalTo("uploadName"));
-    }
-
-    private DefaultConfiguration createNamedConfiguration(String confName) {
-        return new DefaultConfiguration(confName, confName, configurationContainer, dependencyResolver, listenerManager, metaDataProvider, resolutionStrategy);
-    }
-    
-    private DefaultConfiguration createNamedConfiguration(String path, String confName) {
-        return new DefaultConfiguration(path, confName, configurationContainer, dependencyResolver, listenerManager, metaDataProvider, resolutionStrategy);
-    }
-
-    @SuppressWarnings("unchecked")
-    @Test
-    public void buildArtifacts() {
-        final Task otherConfTaskMock = context.mock(Task.class, "otherConfTask");
-        final Task artifactTaskMock = context.mock(Task.class, "artifactTask");
-        final Configuration otherConfiguration = context.mock(Configuration.class);
-        final TaskDependency otherArtifactTaskDependencyMock = context.mock(TaskDependency.class, "otherConfTaskDep");
-        final PublishArtifact otherArtifact = context.mock(PublishArtifact.class, "otherArtifact");
-        final PublishArtifactSet inheritedArtifacts = new DefaultPublishArtifactSet("artifacts", toDomainObjectSet(PublishArtifact.class, otherArtifact));
-        DefaultPublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
-        artifact.builtBy(artifactTaskMock);
-        configuration.getArtifacts().add(artifact);
-
-        context.checking(new Expectations() {{
-            allowing(otherConfiguration).getHierarchy();
-            will(returnValue(toSet()));
-
-            allowing(otherConfiguration).getAllArtifacts();
-            will(returnValue(inheritedArtifacts));
-
-            allowing(otherConfiguration).getAllDependencies();
-
-            allowing(otherArtifact).getBuildDependencies();
-            will(returnValue(otherArtifactTaskDependencyMock));
-            
-            allowing(otherArtifactTaskDependencyMock).getDependencies(with(any(Task.class)));
-            will(returnValue(toSet(otherConfTaskMock)));
-        }});
-        configuration.setExtendsFrom(toSet(otherConfiguration));
-        assertThat((Set<Task>) configuration.getAllArtifacts().getBuildDependencies().getDependencies(context.mock(Task.class, "caller")),
-                equalTo(toSet(artifactTaskMock, otherConfTaskMock)));
-    }
-
-    @Test
-    public void getAllArtifactFiles() {
-        final Task otherConfTaskMock = context.mock(Task.class, "otherConfTask");
-        final Task artifactTaskMock = context.mock(Task.class, "artifactTask");
-        final Configuration otherConfiguration = context.mock(Configuration.class);
-        final TaskDependency otherConfTaskDependencyMock = context.mock(TaskDependency.class, "otherConfTaskDep");
-        final TaskDependency artifactTaskDependencyMock = context.mock(TaskDependency.class, "artifactTaskDep");
-        final File artifactFile1 = new File("artifact1");
-        final File artifactFile2 = new File("artifact2");
-        final PublishArtifact artifact = context.mock(PublishArtifact.class, "artifact");
-        final PublishArtifact otherArtifact = context.mock(PublishArtifact.class, "otherArtifact");
-        final PublishArtifactSet otherArtifacts = new DefaultPublishArtifactSet("artifacts", toDomainObjectSet(PublishArtifact.class, otherArtifact));
-
-        context.checking(new Expectations() {{
-            allowing(otherConfiguration).getHierarchy();
-            will(returnValue(toSet()));
-
-            allowing(otherConfiguration).getAllArtifacts();
-            will(returnValue(otherArtifacts));
-
-            allowing(otherConfiguration).getAllDependencies();
-
-            allowing(otherConfiguration).getExtendsFrom();
-            will(returnValue(toSet()));
-
-            allowing(otherConfiguration).getArtifacts();
-            will(returnValue(toSet(otherArtifact)));
-
-            allowing(otherConfTaskDependencyMock).getDependencies(with(any(Task.class)));
-            will(returnValue(toSet(otherConfTaskMock)));
-
-            allowing(artifactTaskDependencyMock).getDependencies(with(any(Task.class)));
-            will(returnValue(toSet(artifactTaskMock)));
-
-            allowing(artifact).getFile();
-            will(returnValue(artifactFile1));
-
-            allowing(otherArtifact).getFile();
-            will(returnValue(artifactFile2));
-
-            allowing(artifact).getBuildDependencies();
-            will(returnValue(artifactTaskDependencyMock));
-
-            allowing(otherArtifact).getBuildDependencies();
-            will(returnValue(otherConfTaskDependencyMock));
-        }});
-
-        configuration.getArtifacts().add(artifact);
-        configuration.setExtendsFrom(toSet(otherConfiguration));
-
-        FileCollection files = configuration.getAllArtifacts().getFiles();
-        assertThat(files.getFiles(), equalTo(toSet(artifactFile1, artifactFile2)));
-        assertThat(files.getBuildDependencies().getDependencies(null), equalTo((Set) toSet(otherConfTaskMock, artifactTaskMock)));
-    }
-
-    @Test
-    public void buildDependenciesDelegatesToAllSelfResolvingDependencies() {
-        final Task target = context.mock(Task.class, "target");
-        final Task projectDepTaskDummy = context.mock(Task.class, "projectDepTask");
-        final Task fileDepTaskDummy = context.mock(Task.class, "fileDepTask");
-        final ProjectDependency projectDependencyStub = context.mock(ProjectDependency.class);
-        final FileCollectionDependency fileCollectionDependencyStub = context.mock(FileCollectionDependency.class);
-
-        context.checking(new Expectations() {{
-            TaskDependency projectTaskDependencyDummy = context.mock(TaskDependency.class, "projectDep");
-            TaskDependency fileTaskDependencyStub = context.mock(TaskDependency.class, "fileDep");
-
-            allowing(projectDependencyStub).getBuildDependencies();
-            will(returnValue(projectTaskDependencyDummy));
-
-            allowing(projectTaskDependencyDummy).getDependencies(target);
-            will(returnValue(toSet(projectDepTaskDummy)));
-
-            allowing(fileCollectionDependencyStub).getBuildDependencies();
-            will(returnValue(fileTaskDependencyStub));
-
-            allowing(fileTaskDependencyStub).getDependencies(target);
-            will(returnValue(toSet(fileDepTaskDummy)));
-        }});
-
-        configuration.getDependencies().add(projectDependencyStub);
-        configuration.getDependencies().add(fileCollectionDependencyStub);
-
-        assertThat(configuration.getBuildDependencies().getDependencies(target), equalTo((Set) toSet(fileDepTaskDummy,
-                projectDepTaskDummy)));
-    }
-
-    @Test
-    public void buildDependenciesDelegatesToInheritedConfigurations() {
-        final Task target = context.mock(Task.class, "target");
-        final Task otherConfTaskMock = context.mock(Task.class, "otherConfTask");
-        final TaskDependency dependencyTaskDependencyStub = context.mock(TaskDependency.class, "otherConfTaskDep");
-        final Configuration otherConfiguration = context.mock(Configuration.class, "otherConf");
-        final FileCollectionDependency fileCollectionDependencyStub = context.mock(FileCollectionDependency.class);
-        final DependencySet inherited = new DefaultDependencySet("dependencies", toDomainObjectSet(Dependency.class, fileCollectionDependencyStub));
-
-        context.checking(new Expectations() {{
-            allowing(otherConfiguration).getHierarchy();
-            will(returnValue(toSet()));
-
-            allowing(otherConfiguration).getAllArtifacts();
-
-            allowing(otherConfiguration).getAllDependencies();
-            will(returnValue(inherited));
-
-            allowing(fileCollectionDependencyStub).getBuildDependencies();
-            will(returnValue(dependencyTaskDependencyStub));
-
-            allowing(dependencyTaskDependencyStub).getDependencies(target);
-            will(returnValue(toSet(otherConfTaskMock)));
-        }});
-
-        configuration.extendsFrom(otherConfiguration);
-
-        assertThat(configuration.getBuildDependencies().getDependencies(target), equalTo((Set) toSet(otherConfTaskMock)));
-    }
-
-    @SuppressWarnings("unchecked")
-    @Test public void taskDependencyFromProjectDependencyUsingNeeded() {
-        Configuration superConfig = createNamedConfiguration("superConf");
-        configuration.extendsFrom(superConfig);
-
-        final ProjectDependency projectDependencyStub = context.mock(ProjectDependency.class);
-        superConfig.getDependencies().add(projectDependencyStub);
-
-        final Project projectStub = context.mock(Project.class);
-        final TaskContainer taskContainerStub = context.mock(TaskContainer.class);
-        final Task taskStub = context.mock(Task.class);
-        final String taskName = "testit";
-
-        context.checking(new Expectations() {{
-            allowing(projectDependencyStub).getDependencyProject(); will(returnValue(projectStub));
-            allowing(projectStub).getTasks(); will(returnValue(taskContainerStub));
-            allowing(taskContainerStub).findByName(taskName); will(returnValue(taskStub));
-        }});
-
-        TaskDependency td = configuration.getTaskDependencyFromProjectDependency(true, taskName);
-        Task unusedTask = context.mock(Task.class, "unused");
-
-        assertThat((Set<Task>) td.getDependencies(unusedTask), equalTo(toSet(taskStub)));
-    }
-
-    @SuppressWarnings("unchecked")
-    @Test public void taskDependencyFromProjectDependencyUsingDependents() {
-        final String configName = configuration.getName();
-        final String taskName = "testit";
-        final Task tdTask = context.mock(Task.class, "tdTask");
-        final Project taskProject = context.mock(Project.class, "taskProject");
-        final Project rootProject = context.mock(Project.class, "rootProject");
-        final Project dependentProject = context.mock(Project.class, "dependentProject");
-        final Task desiredTask = context.mock(Task.class, "desiredTask");
-        final Set<Task> taskSet = toSet(desiredTask);
-        final ConfigurationContainer configurationContainer = context.mock(ConfigurationContainer.class);
-        final Configuration dependentConfig = context.mock(Configuration.class);
-        final ProjectDependency projectDependency = context.mock(ProjectDependency.class);
-        final Set<ProjectDependency> projectDependencies = toDomainObjectSet(ProjectDependency.class, projectDependency);
-        final DependencySet otherDependencies = context.mock(DependencySet.class);
-
-        context.checking(new Expectations() {{
-            allowing(tdTask).getProject(); will(returnValue(taskProject));
-            allowing(taskProject).getRootProject(); will(returnValue(rootProject));
-            allowing(rootProject).getTasksByName(taskName, true); will(returnValue(taskSet));
-            allowing(desiredTask).getProject(); will(returnValue(dependentProject));
-            allowing(dependentProject).getConfigurations(); will(returnValue(configurationContainer));
-            allowing(configurationContainer).findByName(configName); will(returnValue(dependentConfig));
-
-            allowing(dependentConfig).getAllDependencies(); will(returnValue(otherDependencies));
-            allowing(otherDependencies).withType(ProjectDependency.class); will(returnValue(projectDependencies));
-            allowing(projectDependency).getDependencyProject(); will(returnValue(taskProject));
-        }});
-
-        TaskDependency td = configuration.getTaskDependencyFromProjectDependency(false, taskName);
-        assertThat((Set<Task>) td.getDependencies(tdTask), equalTo(toSet(desiredTask)));
-    }
-
-    @SuppressWarnings("unchecked")
-    @Test public void taskDependencyFromProjectDependencyWithoutCommonConfiguration() {
-        // This test exists because a NullPointerException was thrown by
-        // getTaskDependencyFromProjectDependency() if the rootProject
-        // defined a task as the same name as a subproject's task, but did
-        // not define the same configuration.
-        final String configName = configuration.getName();
-        final String taskName = "testit";
-        final Task tdTask = context.mock(Task.class, "tdTask");
-        final Project taskProject = context.mock(Project.class, "taskProject");
-        final Project rootProject = context.mock(Project.class, "rootProject");
-        final Project dependentProject = context.mock(Project.class, "dependentProject");
-        final Task desiredTask = context.mock(Task.class, "desiredTask");
-        final Set<Task> taskSet = toSet(desiredTask);
-        final ConfigurationContainer configurationContainer = context.mock(ConfigurationContainer.class);
-
-        context.checking(new Expectations() {{
-            allowing(tdTask).getProject(); will(returnValue(taskProject));
-            allowing(taskProject).getRootProject(); will(returnValue(rootProject));
-            allowing(rootProject).getTasksByName(taskName, true); will(returnValue(taskSet));
-            allowing(desiredTask).getProject(); will(returnValue(dependentProject));
-            allowing(dependentProject).getConfigurations(); will(returnValue(configurationContainer));
-
-            // return null to mock not finding the given configuration
-            allowing(configurationContainer).findByName(configName); will(returnValue(null));
-        }});
-
-        TaskDependency td = configuration.getTaskDependencyFromProjectDependency(false, taskName);
-        assertThat(td.getDependencies(tdTask), equalTo(Collections.EMPTY_SET));
-    }
-
-
-    @Test
-    public void getDependencies() {
-        Dependency dependency = context.mock(Dependency.class);
-        configuration.getDependencies().add(dependency);
-        assertThat(configuration.getDependencies(), equalTo(toSet(dependency)));
-    }
-
-    @Test
-    public void getTypedDependencies() {
-        ProjectDependency projectDependency = context.mock(ProjectDependency.class);
-        configuration.getDependencies().add(context.mock(Dependency.class));
-        configuration.getDependencies().add(projectDependency);
-        assertThat(configuration.getDependencies().withType(ProjectDependency.class), equalTo(toSet(projectDependency)));
-    }
-
-    @Test
-    public void getTypedDependenciesReturnsEmptySetWhenNoMatches() {
-        configuration.getDependencies().add(context.mock(Dependency.class));
-        assertThat(configuration.getDependencies().withType(ProjectDependency.class), isEmpty());
-    }
-
-    @Test
-    public void getAllDependencies() {
-        Dependency dependencyConf = HelperUtil.createDependency("group1", "name1", "version1");
-        Dependency dependencyOtherConf1 = HelperUtil.createDependency("group1", "name1", "version1");
-        Dependency dependencyOtherConf2 = context.mock(Dependency.class, "dep2");
-        Configuration otherConf = createNamedConfiguration("otherConf");
-        configuration.getDependencies().add(dependencyConf);
-        configuration.extendsFrom(otherConf);
-        otherConf.getDependencies().add(dependencyOtherConf1);
-        otherConf.getDependencies().add(dependencyOtherConf2);
-
-        assertThat(configuration.getAllDependencies(), equalTo(toSet(dependencyConf, dependencyOtherConf2)));
-        assertCorrectInstanceInAllDependencies(configuration.getAllDependencies(), dependencyConf);
-    }
-
-    @Test
-    public void getAllTypedDependencies() {
-        ProjectDependency projectDependencyCurrentConf = context.mock(ProjectDependency.class, "projectDepCurrentConf");
-        configuration.getDependencies().add(context.mock(Dependency.class, "depCurrentConf"));
-        configuration.getDependencies().add(projectDependencyCurrentConf);
-        Configuration otherConf = createNamedConfiguration("otherConf");
-        configuration.extendsFrom(otherConf);
-        ProjectDependency projectDependencyExtendedConf = context.mock(ProjectDependency.class, "projectDepExtendedConf");
-        otherConf.getDependencies().add(context.mock(Dependency.class, "depExtendedConf"));
-        otherConf.getDependencies().add(projectDependencyExtendedConf);
-
-        assertThat(configuration.getAllDependencies().withType(ProjectDependency.class), equalTo(toSet(projectDependencyCurrentConf, projectDependencyExtendedConf)));
-    }
-
-    @Test
-    public void getAllTypedDependenciesReturnsEmptySetWhenNoMatches() {
-        configuration.getDependencies().add(context.mock(Dependency.class, "depCurrentConf"));
-        Configuration otherConf = createNamedConfiguration("otherConf");
-        configuration.extendsFrom(otherConf);
-        otherConf.getDependencies().add(context.mock(Dependency.class, "depExtendedConf"));
-
-        assertThat(configuration.getAllDependencies().withType(ProjectDependency.class), isEmpty());
-    }
-
-    @Test
-    public void getAllArtifacts() {
-        PublishArtifact artifactConf = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
-        PublishArtifact artifactOtherConf2 = HelperUtil.createPublishArtifact("name2", "ext2", "type2", "classifier2");
-        Configuration otherConf = createNamedConfiguration("otherConf");
-        configuration.getArtifacts().add(artifactConf);
-        configuration.extendsFrom(otherConf);
-        otherConf.getArtifacts().add(artifactOtherConf2);
-        assertThat(configuration.getAllArtifacts(), equalTo(toSet(artifactConf, artifactOtherConf2)));
-    }
-
-    @Test
-    public void artifactAddedAction() {
-        final TestClosure closure = context.mock(TestClosure.class);
-        final PublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
-
-        context.checking(new Expectations() {{
-            one(closure).call(artifact);
-        }});
-
-        configuration.getArtifacts().whenObjectAdded(HelperUtil.toClosure(closure));
-        configuration.getArtifacts().add(artifact);
-    }
-
-    @Test
-    public void artifactRemovedAction() {
-        final TestClosure closure = context.mock(TestClosure.class);
-        final PublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
-
-        configuration.getArtifacts().add(artifact);
-
-        context.checking(new Expectations() {{
-            one(closure).call(artifact);
-        }});
-
-        configuration.getArtifacts().whenObjectRemoved(HelperUtil.toClosure(closure));
-
-        configuration.getArtifacts().remove(artifact);
-    }
-
-    @Test
-    public void removeArtifact() {
-        PublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
-        configuration.getArtifacts().add(artifact);
-        configuration.getArtifacts().remove(artifact);
-        assertThat(configuration.getAllArtifacts(), equalTo(Collections.<PublishArtifact>emptySet()));
-    }
-
-    @Test
-    public void removeArtifactWithUnknownArtifact() {
-        PublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
-        configuration.getArtifacts().add(artifact);
-        configuration.getArtifacts().remove(HelperUtil.createPublishArtifact("name2", "ext1", "type1", "classifier1"));
-        assertThat(configuration.getAllArtifacts(), equalTo(WrapUtil.toSet(artifact)));
-    }
-
-    private void assertCorrectInstanceInAllDependencies(Set<Dependency> allDependencies, Dependency correctInstance) {
-        for (Dependency dependency : allDependencies) {
-            if (dependency == correctInstance) {
-                return;
-            }
-        }
-        fail("Correct instance is missing!");
-    }
-
-    @Test
-    public void copy() {
-        prepareConfigurationForCopyTest();
-
-        Configuration copiedConfiguration = configuration.copy();
-
-        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, configuration.getDependencies());
-    }
-
-    @Test
-    public void copyWithSpec() {
-        prepareConfigurationForCopyTest();
-        Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getDependencies());
-        configuration.getDependencies().add(HelperUtil.createDependency("group3", "name3", "version3"));
-
-        Configuration copiedConfiguration = configuration.copy(new Spec<Dependency>() {
-            public boolean isSatisfiedBy(Dependency element) {
-                return !element.getGroup().equals("group3");
-            }
-        });
-
-        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, expectedDependenciesToCopy);
-    }
-
-    @Test
-    public void copyWithClosure() {
-        prepareConfigurationForCopyTest();
-        Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getDependencies());
-        configuration.getDependencies().add(HelperUtil.createDependency("group3", "name3", "version3"));
-
-        Closure specClosure = HelperUtil.toClosure("{ element ->  !element.group.equals(\"group3\")}");
-        Configuration copiedConfiguration = configuration.copy(specClosure);
-
-        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, expectedDependenciesToCopy);
-    }
-
-    private void prepareConfigurationForCopyTest() {
-        configuration.setVisible(false);
-        configuration.setTransitive(false);
-        configuration.setDescription("descript");
-        configuration.exclude(toMap("group", "value"));
-        configuration.exclude(toMap("group", "value2"));
-        configuration.getArtifacts().add(HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1"));
-        configuration.getArtifacts().add(HelperUtil.createPublishArtifact("name2", "ext2", "type2", "classifier2"));
-        configuration.getDependencies().add(HelperUtil.createDependency("group1", "name1", "version1"));
-        configuration.getDependencies().add(HelperUtil.createDependency("group2", "name2", "version2"));
-    }
-
-    private void assertThatCopiedConfigurationHasElementsAndName(Configuration copiedConfiguration, Set<Dependency> expectedDependencies) {
-        assertThat(copiedConfiguration.getName(), equalTo(configuration.getName() + "Copy"));
-        assertThat(copiedConfiguration.isVisible(), equalTo(configuration.isVisible()));
-        assertThat(copiedConfiguration.isTransitive(), equalTo(configuration.isTransitive()));
-        assertThat(copiedConfiguration.getDescription(), equalTo(configuration.getDescription()));
-        assertThat(asSet(copiedConfiguration.getAllArtifacts()), equalTo(asSet(configuration.getAllArtifacts())));
-        assertThat(copiedConfiguration.getExcludeRules(), equalTo(configuration.getExcludeRules()));
-        assertThat(copiedConfiguration.getExcludeRules().iterator().next(), not(sameInstance(configuration.getExcludeRules().iterator().next())));
-        assertThat(WrapUtil.asSet(copiedConfiguration.getDependencies()), equalTo(WrapUtil.asSet(expectedDependencies)));
-        assertNotSameInstances(copiedConfiguration.getDependencies(), expectedDependencies);
-    }
-
-    @Test
-    public void copyRecursive() {
-        prepareConfigurationForCopyRecursiveTest();
-
-        Configuration copiedConfiguration = configuration.copyRecursive();
-
-        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, configuration.getAllDependencies());
-    }
-
-    @Test
-    public void copyRecursiveWithSpec() {
-        prepareConfigurationForCopyRecursiveTest();
-        Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getAllDependencies());
-        configuration.getDependencies().add(HelperUtil.createDependency("group3", "name3", "version3"));
-
-        Closure specClosure = HelperUtil.toClosure("{ element ->  !element.group.equals(\"group3\")}");
-        Configuration copiedConfiguration = configuration.copyRecursive(specClosure);
-
-        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, expectedDependenciesToCopy);
-    }
-
-    @Test
-    public void copyRecursiveWithClosure() {
-        prepareConfigurationForCopyRecursiveTest();
-        Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getAllDependencies());
-        configuration.getDependencies().add(HelperUtil.createDependency("group3", "name3", "version3"));
-
-        Configuration copiedConfiguration = configuration.copyRecursive(new Spec<Dependency>() {
-            public boolean isSatisfiedBy(Dependency element) {
-                return !element.getGroup().equals("group3");
-            }
-        });
-
-        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, expectedDependenciesToCopy);
-    }
-
-    private void prepareConfigurationForCopyRecursiveTest() {
-        prepareConfigurationForCopyTest();
-        Dependency similarDependency2InOtherConf = HelperUtil.createDependency("group2", "name2", "version2");
-        Dependency otherConfDependency = HelperUtil.createDependency("group4", "name4", "version4");
-        Configuration otherConf = createNamedConfiguration("otherConf");
-        otherConf.getDependencies().add(similarDependency2InOtherConf);
-        otherConf.getDependencies().add(otherConfDependency);
-        configuration.extendsFrom(otherConf);
-    }
-
-    private void assertNotSameInstances(Set<Dependency> dependencies, Set<Dependency> otherDependencies) {
-        for (Dependency dependency : dependencies) {
-            assertHasEqualButNotSameInstance(dependency, otherDependencies);
-        }
-    }
-
-    private void assertHasEqualButNotSameInstance(Dependency dependency, Set<Dependency> otherDependencies) {
-        assertThat(otherDependencies, hasItem(dependency));
-        for (Dependency otherDependency : otherDependencies) {
-            if (otherDependency.equals(dependency)) {
-                assertThat(otherDependency, not(sameInstance(dependency)));
-            }
-        }
-    }
-
-    @Test
-    public void propertyChangeWithNonUnresolvedStateShouldThrowEx() {
-        makeResolveReturnFileSet(new HashSet<File>());
-        configuration.resolve();
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.setTransitive(true);
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.setDescription("someDesc");
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.setExcludeRules(new HashSet<ExcludeRule>());
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.setExtendsFrom(new HashSet<Configuration>());
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.setVisible(true);
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.getDependencies().add(context.mock(Dependency.class));
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.getDependencies().add(context.mock(Dependency.class));
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.exclude(new HashMap<String, String>());
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.extendsFrom(context.mock(Configuration.class));
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.getArtifacts().add(context.mock(PublishArtifact.class));
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.getArtifacts().remove(context.mock(PublishArtifact.class, "removeArtifact"));
-            }
-        });
-        assertInvalidUserDataException(new Executer() {
-            public void execute() {
-                configuration.getArtifacts().add(context.mock(PublishArtifact.class, "removeArtifact"));
-            }
-        });
-    }
-    
-    @Test
-    public void dumpString() {
-        Dependency configurationDependency = HelperUtil.createDependency("dumpgroup1", "dumpname1", "dumpversion1");
-        Dependency otherConfSimilarDependency = HelperUtil.createDependency("dumpgroup1", "dumpname1", "dumpversion1");
-        Dependency otherConfDependency = HelperUtil.createDependency("dumpgroup2", "dumpname2", "dumpversion2");
-        Configuration otherConf = createNamedConfiguration("dumpConf");
-        configuration.extendsFrom(otherConf);
-        otherConf.getDependencies().add(otherConfDependency);
-        otherConf.getDependencies().add(otherConfSimilarDependency);
-        configuration.getDependencies().add(configurationDependency);
-
-        assertThat(configuration.dump(),
-                containsString(
-                "\nConfiguration:"
-                + "  class='class org.gradle.api.internal.artifacts.configurations.DefaultConfiguration'"
-                + "  name='name'"
-                + "  hashcode='"+ configuration.hashCode() +"'"
-                + "\nLocal Dependencies:"
-                + "\n   DefaultExternalModuleDependency{group='dumpgroup1', name='dumpname1', version='dumpversion1', configuration='default'}"
-                + "\nLocal Artifacts:"
-                + "\n   none"
-                + "\nAll Dependencies:"
-                + "\n   DefaultExternalModuleDependency{group='dumpgroup1', name='dumpname1', version='dumpversion1', configuration='default'}"
-                + "\n   DefaultExternalModuleDependency{group='dumpgroup2', name='dumpname2', version='dumpversion2', configuration='default'}"
-                + "\nAll Artifacts:"
-                + "\n   none"));
-    }
-
-    private void assertInvalidUserDataException(Executer executer) {
-        try {
-            executer.execute();
-            fail();
-        } catch (InvalidUserDataException e) {
-            // ignore
-        }
-    }
-
-    private static interface Executer {
-        void execute();
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultResolutionStrategyTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultResolutionStrategyTest.groovy
deleted file mode 100644
index 5660b2d..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultResolutionStrategyTest.groovy
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.configurations;
-
-
-import spock.lang.Specification
-
-/**
- * by Szczepan Faber, created at: 11/2/11
- */
-public class DefaultResolutionStrategyTest extends Specification {
-
-    def strategy = new DefaultResolutionStrategy()
-
-    def "allows setting forced modules"() {
-        expect:
-        strategy.forcedModules.empty
-
-        when:
-        strategy.force 'org.foo:bar:1.0', 'org.foo:baz:2.0'
-
-        then:
-        def versions = strategy.forcedModules as List
-        versions.size() == 2
-
-        versions[0].group == 'org.foo'
-        versions[0].name == 'bar'
-        versions[0].version == '1.0'
-
-        versions[1].group == 'org.foo'
-        versions[1].name == 'baz'
-        versions[1].version == '2.0'
-    }
-
-    def "allows replacing forced modules"() {
-        given:
-        strategy.force 'org.foo:bar:1.0'
-
-        when:
-        strategy.forcedModules = ['hello:world:1.0', [group:'g', name:'n', version:'1']]
-
-        then:
-        def versions = strategy.forcedModules as List
-        versions.size() == 2
-        versions[0].group == 'hello'
-        versions[1].group == 'g'
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ForcedModuleNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ForcedModuleNotationParserTest.groovy
deleted file mode 100644
index 840da8f..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ForcedModuleNotationParserTest.groovy
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.configurations;
-
-
-import org.gradle.api.InvalidUserDataException
-import spock.lang.Specification
-
-/**
- * by Szczepan Faber, created at: 10/14/11
- */
-public class ForcedModuleNotationParserTest extends Specification {
-
-    def "understands group:name:version notation"() {
-        when:
-        def v = new ForcedModuleNotationParser().parseNotation("org.foo:bar:1.0") as List
-
-        then:
-        v.size() == 1
-        v[0].group == 'org.foo'
-        v[0].name  == 'bar'
-        v[0].version  == '1.0'
-    }
-
-    def "works with CharSequences"() {
-        when:
-        def sb = new StringBuilder().append("org.foo:charsequence:1.0")
-        def v = new ForcedModuleNotationParser().parseNotation(sb) as List
-
-        then:
-        v.size() == 1
-        v[0].name  == 'charsequence'
-    }
-
-    def "allows exact type on input"() {
-        def id = ForcedModuleNotationParser.selector("org.foo", "bar", "2.0")
-
-        when:
-        def v = new ForcedModuleNotationParser().parseNotation(id) as List
-
-        then:
-        v.size() == 1
-        v[0].group == 'org.foo'
-        v[0].name  == 'bar'
-        v[0].version  == '2.0'
-    }
-
-    def "allows list of objects on input"() {
-        def id = ForcedModuleNotationParser.selector("org.foo", "bar", "2.0")
-
-        when:
-        def v = new ForcedModuleNotationParser().parseNotation([id, ["hey:man:1.0"], [group:'i', name:'like', version:'maps']]) as List
-
-        then:
-        v.size() == 3
-        v[0].name == 'bar'
-        v[1].name == 'man'
-        v[2].name == 'like'
-    }
-
-    def "allows map on input"() {
-        when:
-        def v = new ForcedModuleNotationParser().parseNotation([group: 'org.foo', name: 'bar', version:'1.0']) as List
-
-        then:
-        v.size() == 1
-        v[0].group == 'org.foo'
-        v[0].name  == 'bar'
-        v[0].version  == '1.0'
-    }
-
-    def "fails for unknown types"() {
-        when:
-        new ForcedModuleNotationParser().parseNotation(new Object())
-
-        then:
-        thrown(InvalidUserDataException)
-    }
-
-    def "reports missing keys for map notation"() {
-        when:
-        new ForcedModuleNotationParser().parseNotation([name: "bar", version: "1.0"])
-
-        then:
-        thrown(InvalidUserDataException)
-    }
-
-    def "reports wrong keys for map notation"() {
-        when:
-        new ForcedModuleNotationParser().parseNotation([groop: 'groop', name: "bar", version: "1.0"])
-
-        then:
-        thrown(InvalidUserDataException)
-    }
-
-    def "reports invalid format for string notation"() {
-        when:
-        new ForcedModuleNotationParser().parseNotation(["blahblah"])
-
-        then:
-        thrown(InvalidUserDataException)
-    }
-
-    def "reports invalid missing data for string notation"() {
-        when:
-        new ForcedModuleNotationParser().parseNotation([":foo:"])
-
-        then:
-        def ex = thrown(InvalidUserDataException)
-        ex.message.contains 'cannot be empty'
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/DefaultCachePolicySpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/DefaultCachePolicySpec.groovy
deleted file mode 100644
index 92dc7ee..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/DefaultCachePolicySpec.groovy
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.configurations.dynamicversion;
-
-
-import org.gradle.api.Action
-import org.gradle.api.artifacts.ModuleVersionIdentifier
-import org.gradle.api.artifacts.ResolvedModuleVersion
-import org.gradle.api.artifacts.cache.ArtifactResolutionControl
-import org.gradle.api.artifacts.cache.DependencyResolutionControl
-import org.gradle.api.artifacts.cache.ModuleResolutionControl
-import org.gradle.api.internal.artifacts.DefaultArtifactIdentifier
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
-import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
-import spock.lang.Specification
-
-import java.util.concurrent.TimeUnit
-
-public class DefaultCachePolicySpec extends Specification {
-    private static final int SECOND = 1000;
-    private static final int MINUTE = SECOND * 60;
-    private static final int HOUR = MINUTE * 60;
-    private static final int DAY = HOUR * 24;
-    private static final int WEEK = DAY * 7;
-    private static final int FOREVER = Integer.MAX_VALUE
-
-    DefaultCachePolicy cachePolicy = new DefaultCachePolicy()
-
-    def "will cache default"() {
-        expect:
-        hasDynamicVersionTimeout(DAY)
-        hasChangingModuleTimeout(DAY)
-        hasModuleTimeout(FOREVER)
-        hasMissingArtifactTimeout(DAY)
-        hasMissingModuleTimeout(DAY)
-    }
-
-    def "uses changing module timeout for changing modules"() {
-        when:
-        cachePolicy.cacheChangingModulesFor(10, TimeUnit.SECONDS);
-
-        then:
-        hasDynamicVersionTimeout(DAY);
-        hasChangingModuleTimeout(10 * SECOND)
-        hasModuleTimeout(FOREVER)
-        hasMissingModuleTimeout(DAY)
-        hasMissingArtifactTimeout(DAY)
-    }
-
-    def "uses dynamic version timeout for dynamic versions"() {
-        when:
-        cachePolicy.cacheDynamicVersionsFor(10, TimeUnit.SECONDS)
-
-        then:
-        hasDynamicVersionTimeout(10 * SECOND)
-        hasChangingModuleTimeout(DAY)
-        hasMissingModuleTimeout(DAY)
-        hasMissingArtifactTimeout(DAY)
-        hasModuleTimeout(FOREVER)
-    }
-
-    def "applies invalidate rule for dynamic versions"() {
-        when:
-        cachePolicy.eachDependency(new Action<DependencyResolutionControl>() {
-            void execute(DependencyResolutionControl t) {
-                t.refresh()
-            }
-        })
-
-        then:
-        cachePolicy.mustRefreshDynamicVersion(null, null, 2 * SECOND)
-    }
-
-    def "applies useCachedResult for dynamic versions"() {
-        when:
-        cachePolicy.eachDependency(new Action<DependencyResolutionControl>() {
-            void execute(DependencyResolutionControl t) {
-                t.useCachedResult()
-            }
-        })
-
-        then:
-        !cachePolicy.mustRefreshDynamicVersion(null, null, 2 * SECOND)
-    }
-
-    def "applies cacheFor rules for dynamic versions"() {
-        when:
-        cachePolicy.eachDependency(new Action<DependencyResolutionControl>() {
-            void execute(DependencyResolutionControl t) {
-                t.cacheFor(100, TimeUnit.SECONDS)
-            }
-        })
-
-        then:
-        hasDynamicVersionTimeout(100 * SECOND)
-    }
-    
-    def "provides details of cached dynamic version"() {
-        expect:
-        cachePolicy.eachDependency(new Action<DependencyResolutionControl>() {
-            void execute(DependencyResolutionControl t) {
-                assertId(t.request, 'g', 'n', 'v')
-                assertId(t.cachedResult, 'group', 'name', 'version')
-                t.refresh()
-            }
-        })
-        cachePolicy.mustRefreshDynamicVersion(moduleSelector('g', 'n', 'v'), moduleIdentifier('group', 'name', 'version'), 0)
-    }
-    
-    def "provides details of cached module"() {
-        expect:
-        cachePolicy.eachModule(new Action<ModuleResolutionControl>() {
-            void execute(ModuleResolutionControl t) {
-                assertId(t.request, 'g', 'n', 'v')
-                assertId(t.cachedResult.id, 'group', 'name', 'version')
-                assert !t.changing
-                t.refresh()
-            }
-        })
-        cachePolicy.mustRefreshModule(moduleIdentifier('g', 'n', 'v'), moduleVersion('group', 'name', 'version'), null, 0)
-    }
-    
-    def "provides details of cached changing module"() {
-        expect:
-        cachePolicy.eachModule(new Action<ModuleResolutionControl>() {
-            void execute(ModuleResolutionControl t) {
-                assertId(t.request, 'g', 'n', 'v')
-                assertId(t.cachedResult.id, 'group', 'name', 'version')
-                assert t.changing
-                t.refresh()
-            }
-        })
-        cachePolicy.mustRefreshChangingModule(moduleIdentifier('g', 'n', 'v'), moduleVersion('group', 'name', 'version'), 0)
-    }
-    
-    def "provides details of cached artifact"() {
-        expect:
-        cachePolicy.eachArtifact(new Action<ArtifactResolutionControl>() {
-            void execute(ArtifactResolutionControl t) {
-                assertId(t.request.moduleVersionIdentifier, 'group', 'name', 'version')
-                assert t.request.name == 'artifact'
-                assert t.request.type == 'type'
-                assert t.request.extension == 'ext'
-                assert t.request.classifier == 'classifier'
-                assert t.cachedResult == null
-                t.refresh()
-            }
-        })
-        def artifactIdentifier = new DefaultArtifactIdentifier(moduleIdentifier('group', 'name', 'version'), 'artifact', 'type', 'ext', 'classifier')
-        cachePolicy.mustRefreshArtifact(artifactIdentifier, null, 0)
-    }
-    
-    def "can use cacheFor to control missing module and artifact timeout"() {
-        when:
-        cachePolicy.eachModule(new Action<ModuleResolutionControl>() {
-            void execute(ModuleResolutionControl t) {
-                if (t.cachedResult == null) {
-                    t.cacheFor(10, TimeUnit.SECONDS)
-                }
-            }
-        });
-        cachePolicy.eachArtifact(new Action<ArtifactResolutionControl>() {
-            void execute(ArtifactResolutionControl t) {
-                if (t.cachedResult == null) {
-                    t.cacheFor(20, TimeUnit.SECONDS)
-                }
-            }
-        });
-
-        then:
-        hasDynamicVersionTimeout(DAY)
-        hasChangingModuleTimeout(DAY)
-        hasModuleTimeout(FOREVER)
-        hasMissingModuleTimeout(10 * SECOND)
-        hasMissingArtifactTimeout(20 * SECOND)
-    }
-    
-    private def hasDynamicVersionTimeout(int timeout) {
-        def moduleId = moduleIdentifier('group', 'name', 'version')
-        assert !cachePolicy.mustRefreshDynamicVersion(null, moduleId, 100)
-        assert !cachePolicy.mustRefreshDynamicVersion(null, moduleId, timeout);
-        assert !cachePolicy.mustRefreshDynamicVersion(null, moduleId, timeout - 1)
-        cachePolicy.mustRefreshDynamicVersion(null, moduleId, timeout + 1)
-    }
-
-    private def hasChangingModuleTimeout(int timeout) {
-        def module = moduleVersion('group', 'name', 'version')
-        assert !cachePolicy.mustRefreshChangingModule(null, module, timeout - 1)
-        assert !cachePolicy.mustRefreshChangingModule(null, module, timeout);
-        cachePolicy.mustRefreshChangingModule(null, module, timeout + 1)
-    }
-
-    private def hasModuleTimeout(int timeout) {
-        def module = moduleVersion('group', 'name', 'version')
-        assert !cachePolicy.mustRefreshModule(null, module, null, timeout);
-        assert !cachePolicy.mustRefreshModule(null, module, null, timeout - 1)
-        if (timeout == FOREVER) {
-            return true
-        }
-        cachePolicy.mustRefreshModule(null, module, timeout + 1)
-    }
-
-    private def hasMissingModuleTimeout(int timeout) {
-        assert !cachePolicy.mustRefreshModule(null, null, null, timeout);
-        assert !cachePolicy.mustRefreshModule(null, null, null, timeout - 1)
-        cachePolicy.mustRefreshModule(null, null, null, timeout + 1)
-    }
-
-    private def hasMissingArtifactTimeout(int timeout) {
-        assert !cachePolicy.mustRefreshArtifact(null, null, timeout);
-        assert !cachePolicy.mustRefreshArtifact(null, null, timeout - 1)
-        cachePolicy.mustRefreshArtifact(null, null, timeout + 1)
-    }
-    
-    private def assertId(def moduleId, String group, String name , String version) {
-        assert moduleId.group == group
-        assert moduleId.name == name
-        assert moduleId.version == version
-    }
-    
-    private def moduleSelector(String group, String name, String version) {
-        new DefaultModuleVersionSelector(group, name, version)
-    }
-
-    private def moduleIdentifier(String group, String name, String version) {
-        new DefaultModuleVersionIdentifier(group, name, version)
-    }
-
-    private def moduleVersion(String group, String name, String version) {
-        return new ResolvedModuleVersion() {
-            ModuleVersionIdentifier getId() {
-                return new DefaultModuleVersionIdentifier(group, name, version);
-            }
-        }
-    }
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.java
index 87916ae..69d3668 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.java
@@ -160,6 +160,7 @@ public class DefaultProjectDependencyTest extends AbstractModuleDependencyTest {
     private Task taskInTargetProject(final String name) {
         final Task task = context.mock(Task.class, name);
         context.checking(new Expectations(){{
+            allowing(dependencyProjectStub).ensureEvaluated();
             allowing(dependencyProjectTaskContainerStub).getByName(name);
             will(returnValue(task));
         }});
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryFactoryTest.groovy
deleted file mode 100644
index c174f25..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryFactoryTest.groovy
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.dsl
-
-import org.gradle.api.Action
-import org.gradle.api.artifacts.repositories.FlatDirectoryArtifactRepository
-import org.gradle.api.artifacts.repositories.IvyArtifactRepository
-import org.gradle.api.artifacts.repositories.MavenArtifactRepository
-import org.gradle.api.internal.ClosureBackedAction
-import org.gradle.api.internal.artifacts.BaseRepositoryFactory
-import spock.lang.Specification
-
-import static org.gradle.api.artifacts.ArtifactRepositoryContainer.DEFAULT_MAVEN_CENTRAL_REPO_NAME
-import static org.gradle.api.artifacts.ArtifactRepositoryContainer.DEFAULT_MAVEN_LOCAL_REPO_NAME
-
-class DefaultRepositoryFactoryTest extends Specification {
-
-    BaseRepositoryFactory baseRepositoryFactory = Mock(BaseRepositoryFactory)
-    DefaultRepositoryFactory factory = new DefaultRepositoryFactory(baseRepositoryFactory)
-
-    Action action(Closure closure) {
-        new ClosureBackedAction(closure)
-    }
-
-    def "flat dir repos are given a default name"() {
-        when:
-        def repo = Mock(FlatDirectoryArtifactRepository)
-        1 * baseRepositoryFactory.createFlatDirRepository() >> repo
-        1 * repo.setName(DefaultRepositoryFactory.FLAT_DIR_DEFAULT_NAME)
-
-        then:
-        factory.flatDir(action { }).is(repo)
-    }
-
-    def "can configure flat dir by action"() {
-        when:
-        def repo = Mock(FlatDirectoryArtifactRepository)
-        1 * baseRepositoryFactory.createFlatDirRepository() >> repo
-        1 * repo.setDirs(['a', 'b'])
-        1 * repo.setName('libs')
-        _ * repo.getName() >> "libs"
-
-        then:
-        factory.flatDir(action { name = 'libs'; dirs = ['a', 'b'] }).is(repo)
-    }
-
-    def "can configure flat dir by map"() {
-        when:
-        def repo = Mock(FlatDirectoryArtifactRepository)
-        1 * baseRepositoryFactory.createFlatDirRepository() >> repo
-        1 * repo.setDirs(['a', 'b'])
-        1 * repo.setName('libs')
-        _ * repo.getName() >> "libs"
-
-        then:
-        factory.flatDir([name: 'libs'] + [dirs: ['a', 'b']]).is(repo)
-    }
-
-    def "can configure flat dir by map, one dir"() {
-        when:
-        def repo = Mock(FlatDirectoryArtifactRepository)
-        1 * baseRepositoryFactory.createFlatDirRepository() >> repo
-        1 * repo.setDirs(['a'])
-        1 * repo.setName('libs')
-        _ * repo.getName() >> "libs"
-
-        then:
-        factory.flatDir([name: 'libs'] + [dirs: 'a']).is(repo)
-    }
-
-    public void testMavenCentralWithNoArgs() {
-        when:
-        MavenArtifactRepository repository = Mock(MavenArtifactRepository)
-        1 * baseRepositoryFactory.createMavenCentralRepository() >> repository
-        1 * repository.setName(DEFAULT_MAVEN_CENTRAL_REPO_NAME)
-
-        then:
-        factory.mavenCentral().is(repository)
-    }
-
-    def testMavenLocalWithNoArgs() {
-        when:
-        MavenArtifactRepository repository = Mock(MavenArtifactRepository)
-        1 * baseRepositoryFactory.createMavenLocalRepository() >> repository
-        1 * repository.setName(DEFAULT_MAVEN_LOCAL_REPO_NAME)
-
-        then:
-        factory.mavenLocal().is(repository)
-    }
-
-    def "ivy repos are assigned a default name"() {
-        when:
-        def repo = Mock(IvyArtifactRepository)
-        baseRepositoryFactory.createIvyRepository() >> repo
-        1 * repo.setName("ivy")
-
-        then:
-        factory.ivy(action { })
-    }
-
-    def testIvyWithAction() {
-        when:
-        def repo = Mock(IvyArtifactRepository)
-        baseRepositoryFactory.createIvyRepository() >> repo
-
-        1 * repo.setName("foo")
-
-        then:
-        factory.ivy(action { name = "foo" })
-    }
-
-    def testMavenWithAction() {
-        when:
-        def repo = Mock(MavenArtifactRepository)
-        baseRepositoryFactory.createMavenRepository() >> repo
-
-        1 * repo.setName("foo")
-
-        then:
-        factory.maven(action { name = "foo" })
-    }
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy
index aab6056..5be9bda 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy
@@ -22,6 +22,7 @@ import org.gradle.api.Action
 import org.gradle.api.artifacts.ArtifactRepositoryContainer
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository
 import org.gradle.api.internal.ThreadGlobalInstantiator
+import org.gradle.api.internal.artifacts.BaseRepositoryFactory
 import org.gradle.api.internal.artifacts.DefaultArtifactRepositoryContainerTest
 import org.gradle.api.internal.artifacts.repositories.FixedResolverArtifactRepository
 import org.gradle.internal.reflect.Instantiator
@@ -29,17 +30,14 @@ import org.junit.Test
 
 class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTest {
 
-    RepositoryFactoryInternal repositoryFactory
     DefaultRepositoryHandler handler
 
     def setup() {
-        repositoryFactory = Mock(RepositoryFactoryInternal)
-        repositoryFactory.getBaseRepositoryFactory() >> baseRepositoryFactory
         handler = createRepositoryHandler()
     }
 
     public ArtifactRepositoryContainer createRepositoryHandler(
-            RepositoryFactoryInternal repositoryFactory = repositoryFactory,
+            BaseRepositoryFactory repositoryFactory = repositoryFactory,
             Instantiator instantiator = ThreadGlobalInstantiator.getOrCreate()
     ) {
         new DefaultRepositoryHandler(repositoryFactory, instantiator)
@@ -48,7 +46,7 @@ class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTes
     def testFlatDirWithClosure() {
         given:
         def repository = Mock(TestFlatDirectoryArtifactRepository)
-        1 * repositoryFactory.flatDir(_ as Action) >> repository
+        1 * repositoryFactory.createFlatDirRepository() >> repository
 
         expect:
         handler.flatDir { name = 'libs' }.is(repository)
@@ -57,7 +55,7 @@ class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTes
     def testFlatDirWithMap() {
         given:
         def repository = Mock(TestFlatDirectoryArtifactRepository)
-        1 * repositoryFactory.flatDir(_ as Map) >> repository
+        1 * repositoryFactory.createFlatDirRepository() >> repository
 
         expect:
         handler.flatDir([name: 'libs'] + [dirs: ['a', 'b']]).is(repository)
@@ -66,7 +64,7 @@ class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTes
     public void testMavenCentralWithNoArgs() {
         when:
         MavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
-        1 * repositoryFactory.mavenCentral() >> repository
+        1 * repositoryFactory.createMavenCentralRepository() >> repository
         repository.getName() >> "name"
 
         then:
@@ -76,7 +74,7 @@ class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTes
     public void testMavenCentralWithMap() {
         when:
         MavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
-        1 * repositoryFactory.mavenCentral() >> repository
+        1 * repositoryFactory.createMavenCentralRepository() >> repository
         1 * repository.setArtifactUrls(["abc"])
         repository.getName() >> "name"
 
@@ -87,7 +85,7 @@ class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTes
     def testMavenLocalWithNoArgs() {
         when:
         MavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
-        1 * repositoryFactory.mavenLocal() >> repository
+        1 * repositoryFactory.createMavenLocalRepository() >> repository
         repository.getName() >> "name"
 
         then:
@@ -102,13 +100,13 @@ class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTes
         String repoName = 'mavenRepoName'
 
         TestMavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
-        repositoryFactory.maven(_ as Action) >> repository
+        repositoryFactory.createMavenRepository() >> repository
         1 * repository.setName(repoName)
         repository.getName() >> repoName
         1 * repository.setUrl(repoRoot)
         1 * repository.setArtifactUrls([testUrl1, testUrl2])
         DependencyResolver resolver = new FileSystemResolver(name: "resolver")
-        1 * baseRepositoryFactory.toResolver(repository) >> resolver
+        1 * repositoryFactory.toResolver(repository) >> resolver
 
         then:
         handler.mavenRepo([name: repoName, url: repoRoot, artifactUrls: [testUrl1, testUrl2]]).is(resolver)
@@ -124,12 +122,12 @@ class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTes
         String repoName = 'mavenRepoName'
 
         TestMavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
-        repositoryFactory.maven(_ as Action) >> repository
+        repositoryFactory.createMavenRepository() >> repository
         1 * repository.setName(repoName)
         repository.getName() >> repoName
         1 * repository.setUrl(repoRoot)
         DependencyResolver resolver = new FileSystemResolver(name: "resolver")
-        1 * baseRepositoryFactory.toResolver(repository) >> resolver
+        1 * repositoryFactory.toResolver(repository) >> resolver
 
         then:
         handler.mavenRepo([name: repoName, url: repoRoot]).is(resolver)
@@ -143,11 +141,11 @@ class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTes
         String repoRoot = 'http://www.reporoot.org'
 
         TestMavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
-        repositoryFactory.maven(_ as Action) >> repository
+        repositoryFactory.createMavenRepository() >> repository
         repository.getName() >> null
         1 * repository.setUrl(repoRoot)
         DependencyResolver resolver = new FileSystemResolver(name: "resolver")
-        1 * baseRepositoryFactory.toResolver(repository) >> resolver
+        1 * repositoryFactory.toResolver(repository) >> resolver
 
         then:
         handler.mavenRepo([url: repoRoot]).is(resolver)
@@ -158,7 +156,7 @@ class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTes
     public void createIvyRepositoryUsingClosure() {
         when:
         def repository = Mock(TestIvyArtifactRepository)
-        1 * repositoryFactory.ivy(_ as Action) >> repository
+        1 * repositoryFactory.createIvyRepository() >> repository
 
         then:
         handler.ivy { }.is repository
@@ -168,7 +166,7 @@ class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTes
         when:
         def repository = Mock(TestIvyArtifactRepository)
         def action = Mock(Action)
-        1 * repositoryFactory.ivy(action) >> repository
+        1 * repositoryFactory.createIvyRepository() >> repository
 
         then:
         handler.ivy(action).is repository
@@ -192,7 +190,7 @@ class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTes
         repo3.getName() >> { repo3Name }
         repo3.setName(_) >> { repo3Name = it[0] }
 
-        repositoryFactory.ivy(_) >>> [repo1, repo2, repo3]
+        repositoryFactory.createIvyRepository() >>> [repo1, repo2, repo3]
 
         when:
         handler.ivy { }
@@ -208,7 +206,7 @@ class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTes
     public void createMavenRepositoryUsingClosure() {
         when:
         MavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
-        1 * repositoryFactory.maven(_ as Action) >> repository
+        1 * repositoryFactory.createMavenRepository() >> repository
 
         then:
         handler.maven { }.is repository
@@ -218,7 +216,7 @@ class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTes
         when:
         MavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
         def action = Mock(Action)
-        1 * repositoryFactory.maven(action) >> repository
+        1 * repositoryFactory.createMavenRepository() >> repository
 
         then:
         handler.maven(action).is repository
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifactTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifactTest.groovy
new file mode 100644
index 0000000..4713781
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifactTest.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.publish
+
+
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.tasks.bundling.AbstractArchiveTask
+import org.gradle.util.WrapUtil
+import org.jmock.Expectations
+import org.junit.Test
+
+import static org.hamcrest.Matchers.equalTo
+import static org.hamcrest.Matchers.sameInstance
+import static org.junit.Assert.assertThat
+
+/**
+ * @author Hans Dockter
+ */
+public class ArchivePublishArtifactTest extends AbstractPublishArtifactTest {
+    private AbstractArchiveTask archiveTask = context.mock(AbstractArchiveTask.class)
+
+    @Override
+    protected PublishArtifact createPublishArtifact(final String classifier) {
+        prepareMocks(classifier, "")
+        return new ArchivePublishArtifact(archiveTask)
+    }
+
+    private void prepareMocks(final String classifier, final String appendix) {
+        context.checking(new Expectations() {
+            {
+                allowing(archiveTask).getExtension()
+                will(returnValue(getTestExt()))
+
+                allowing(archiveTask).getBaseName()
+                will(returnValue(getTestName()))
+
+                allowing(archiveTask).getAppendix()
+                will(returnValue(appendix))
+
+                allowing(archiveTask).getArchivePath()
+                will(returnValue(getTestFile()))
+
+                allowing(archiveTask).getClassifier()
+                will(returnValue(classifier))
+            }
+        })
+    }
+
+    @Override
+    protected String getTestType() {
+        return getTestExt()
+    }
+
+    @Test
+    public void init() {
+        ArchivePublishArtifact publishArtifact = (ArchivePublishArtifact) createPublishArtifact(getTestClassifier())
+        assertThat((Set<AbstractArchiveTask>) publishArtifact.getBuildDependencies().getDependencies(null), equalTo(WrapUtil.toSet(archiveTask)))
+        assertCommonPropertiesAreSet(publishArtifact, true)
+        assertThat(publishArtifact.getArchiveTask(), sameInstance(archiveTask))
+    }
+
+    @Test
+    public void nameWithAppendix() {
+        String testAppendix = "appendix"
+        prepareMocks(getTestClassifier(), testAppendix)
+        PublishArtifact publishArtifact = new ArchivePublishArtifact(archiveTask)
+        assertThat(publishArtifact.getName(), equalTo(getTestName() + "-" + testAppendix))
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifactTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifactTest.java
deleted file mode 100644
index fb831b9..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifactTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2007-2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.publish;
-
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.tasks.bundling.AbstractArchiveTask;
-import org.gradle.util.WrapUtil;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.sameInstance;
-import org.jmock.Expectations;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class ArchivePublishArtifactTest extends AbstractPublishArtifactTest {
-    private AbstractArchiveTask archiveTask = context.mock(AbstractArchiveTask.class);
-    
-    @Override
-    protected PublishArtifact createPublishArtifact(final String classifier) {
-        prepareMocks(classifier, "");
-        return new ArchivePublishArtifact(archiveTask);
-    }
-
-    private void prepareMocks(final String classifier, final String appendix) {
-        context.checking(new Expectations() {{
-            allowing(archiveTask).getExtension();
-            will(returnValue(getTestExt()));
-
-            allowing(archiveTask).getBaseName();
-            will(returnValue(getTestName()));
-
-            allowing(archiveTask).getAppendix();
-            will(returnValue(appendix));
-
-            allowing(archiveTask).getArchivePath();
-            will(returnValue(getTestFile()));
-
-            allowing(archiveTask).getClassifier();
-            will(returnValue(classifier));
-        }});
-    }
-
-    @Override
-    protected String getTestType() {
-        return getTestExt();
-    }
-
-    @Test
-    public void init() {
-        ArchivePublishArtifact publishArtifact = (ArchivePublishArtifact) createPublishArtifact(getTestClassifier());
-        assertThat((Set<AbstractArchiveTask>) publishArtifact.getBuildDependencies().getDependencies(null), equalTo(WrapUtil.toSet(archiveTask)));
-        assertCommonPropertiesAreSet(publishArtifact, true);
-        assertThat(publishArtifact.getArchiveTask(), sameInstance(archiveTask));
-    }
-    
-    @Test
-    public void nameWithAppendix() {
-        String testAppendix = "appendix";
-        prepareMocks(getTestClassifier(), testAppendix);
-        PublishArtifact publishArtifact = new ArchivePublishArtifact(archiveTask);
-        assertThat(publishArtifact.getName(), equalTo(getTestName() + "-" + testAppendix));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CachingHasherTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CachingHasherTest.java
index 9a6d5fc..4082439 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CachingHasherTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CachingHasherTest.java
@@ -17,7 +17,7 @@ package org.gradle.api.internal.changedetection;
 
 import org.gradle.cache.PersistentIndexedCache;
 import org.gradle.messaging.serialize.Serializer;
-import org.gradle.util.TemporaryFolder;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -35,7 +35,7 @@ import static org.junit.Assert.assertThat;
 @RunWith(JMock.class)
 public class CachingHasherTest {
     @Rule
-    TemporaryFolder tmpDir = new TemporaryFolder();
+    TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private final JUnit4Mockery context = new JUnit4Mockery();
     private final Hasher delegate = context.mock(Hasher.class);
     private final PersistentIndexedCache<File, CachingHasher.FileInfo> cache = context.mock(
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotterTest.groovy
index 5bc634d..961a356 100755
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotterTest.groovy
@@ -15,20 +15,20 @@
  */
 package org.gradle.api.internal.changedetection
 
-
-import static org.junit.Assert.*
-import static org.hamcrest.Matchers.*
-
+import org.gradle.api.file.FileCollection
+import org.gradle.api.file.FileTree
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.ChangeListener
 import org.gradle.util.JUnit4GroovyMockery
 import org.jmock.integration.junit4.JMock
-import org.junit.runner.RunWith
-import org.junit.Test
 import org.junit.Rule
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
-import org.gradle.api.file.FileCollection
-import org.gradle.util.ChangeListener
-import org.gradle.api.file.FileTree
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import static org.hamcrest.Matchers.equalTo
+import static org.hamcrest.Matchers.notNullValue
+import static org.junit.Assert.assertThat
 
 @RunWith(JMock.class)
 public class DefaultFileSnapshotterTest {
@@ -38,7 +38,7 @@ public class DefaultFileSnapshotterTest {
     private ChangeListener listener = context.mock(ChangeListener.class)
     private final DefaultFileSnapshotter snapshotter = new DefaultFileSnapshotter(hasher)
     @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder()
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
     @Test
     public void getFilesReturnsOnlyTheFilesWhichExisted() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepositoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepositoryTest.java
index 29d1433..c25ebbc 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepositoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepositoryTest.java
@@ -24,10 +24,10 @@ import org.gradle.api.invocation.Gradle;
 import org.gradle.cache.CacheRepository;
 import org.gradle.cache.internal.DefaultCacheRepository;
 import org.gradle.internal.id.RandomLongIdGenerator;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.testfixtures.internal.InMemoryCacheFactory;
 import org.gradle.util.HelperUtil;
-import org.gradle.util.TemporaryFolder;
-import org.gradle.util.TestFile;
 import org.hamcrest.Matcher;
 import org.junit.Before;
 import org.junit.Rule;
@@ -43,7 +43,7 @@ import static org.junit.Assert.*;
 
 public class DefaultTaskArtifactStateRepositoryTest {
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private final ProjectInternal project = HelperUtil.createRootProject();
     private final Gradle gradle = project.getGradle();
     private final TestFile outputFile = tmpDir.file("output-file");
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/classpath/DefaultModuleRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/classpath/DefaultModuleRegistryTest.groovy
index b70407b..c5a2ed6 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/classpath/DefaultModuleRegistryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/classpath/DefaultModuleRegistryTest.groovy
@@ -15,13 +15,13 @@
  */
 package org.gradle.api.internal.classpath
 
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 class DefaultModuleRegistryTest extends Specification {
-    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     TestFile runtimeDep
     TestFile resourcesDir
     TestFile jarFile
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/classpath/ManifestUtilTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/classpath/ManifestUtilTest.groovy
index 18c73cb..dbe569c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/classpath/ManifestUtilTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/classpath/ManifestUtilTest.groovy
@@ -16,17 +16,18 @@
 
 package org.gradle.api.internal.classpath
 
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
 import java.util.jar.Attributes
 import java.util.jar.JarOutputStream
 import java.util.jar.Manifest
 import java.util.zip.ZipEntry
-import org.gradle.util.TemporaryFolder
-import org.junit.Rule
-import spock.lang.Specification
 
 public class ManifestUtilTest extends Specification {
     @Rule
-    final TemporaryFolder tmpDir = new TemporaryFolder()
+    final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     def jarFile = tmpDir.file("mydir/jarfile.jar").createFile()
 
     def "creates manifest classpath with relative urls"() {
@@ -39,7 +40,7 @@ public class ManifestUtilTest extends Specification {
 
     def "creates manifest classpath with absolute urls"() {
         when:
-        def tmpDirPath = tmpDir.dir.toURI().rawPath
+        def tmpDirPath = tmpDir.testDirectory.toURI().rawPath
         def file1 = tmpDir.file('different/jar1.jar')
         def file2 = tmpDir.file('different/nested/jar2.jar')
         
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/AbstractFileCollectionTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/AbstractFileCollectionTest.java
index 4f23782..564d959 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/AbstractFileCollectionTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/AbstractFileCollectionTest.java
@@ -22,7 +22,11 @@ import org.gradle.api.file.FileVisitorUtil;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.StopExecutionException;
 import org.gradle.api.tasks.TaskDependency;
-import org.gradle.util.*;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
+import org.gradle.util.GUtil;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.JUnit4GroovyMockery;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -42,7 +46,7 @@ import static org.junit.Assert.*;
 @RunWith(JMock.class)
 public class AbstractFileCollectionTest {
     @Rule
-    public final TemporaryFolder testDir = new TemporaryFolder();
+    public final TestNameTestDirectoryProvider testDir = new TestNameTestDirectoryProvider();
     final JUnit4Mockery context = new JUnit4GroovyMockery();
     final TaskDependency dependency = context.mock(TaskDependency.class);
 
@@ -161,7 +165,7 @@ public class AbstractFileCollectionTest {
 
     @Test
     public void includesOnlyExistingFilesWhenAddedToAntBuilderAsAFileSetOrMatchingTask() {
-        TestFile testDir = this.testDir.getDir();
+        TestFile testDir = this.testDir.getTestDirectory();
         TestFile file1 = testDir.file("f1").touch();
         TestFile dir1 = testDir.file("dir1").createDir();
         TestFile file2 = dir1.file("f2").touch();
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/AbstractFileTreeElementTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/AbstractFileTreeElementTest.java
index 2533aa6..5c881ce 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/AbstractFileTreeElementTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/AbstractFileTreeElementTest.java
@@ -18,10 +18,10 @@ package org.gradle.api.internal.file;
 import org.gradle.api.file.RelativePath;
 import org.gradle.internal.nativeplatform.filesystem.Chmod;
 import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.GFileUtils;
 import org.gradle.util.JUnit4GroovyMockery;
-import org.gradle.util.TemporaryFolder;
-import org.gradle.util.TestFile;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.junit.Rule;
@@ -38,7 +38,7 @@ import static org.junit.Assert.assertThat;
 @RunWith(JMock.class)
 public class AbstractFileTreeElementTest {
     @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     final JUnit4GroovyMockery context = new JUnit4GroovyMockery();
     final Chmod chmod = context.mock(Chmod.class);
 
@@ -81,7 +81,7 @@ public class AbstractFileTreeElementTest {
 
     @Test
     public void defaultPermissionValuesAreUsed() {
-        TestFileTreeElement dir = new TestFileTreeElement(tmpDir.getDir());
+        TestFileTreeElement dir = new TestFileTreeElement(tmpDir.getTestDirectory());
         TestFileTreeElement file = new TestFileTreeElement(tmpDir.file("someFile"));
 
         assertThat(dir.getMode(), equalTo(FileSystem.DEFAULT_DIR_MODE));
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverSpec.groovy
index 317ee9f..f2d3317 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverSpec.groovy
@@ -15,21 +15,20 @@
  */
 package org.gradle.api.internal.file
 
+import org.gradle.internal.nativeplatform.filesystem.FileSystems
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.Requires
-import org.gradle.util.TemporaryFolder
 import org.gradle.util.TestPrecondition
-import org.gradle.internal.nativeplatform.filesystem.FileSystems
-
 import org.junit.Rule
 import spock.lang.Specification
 
 class BaseDirFileResolverSpec extends Specification {
-    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
     @Requires(TestPrecondition.SYMLINKS)
     def "normalizes absolute path which points to an absolute link"() {
-        def target = createFile(new File(tmpDir.dir, 'target.txt'))
-        def file = new File(tmpDir.dir, 'a/other.txt')
+        def target = createFile(new File(tmpDir.testDirectory, 'target.txt'))
+        def file = new File(tmpDir.testDirectory, 'a/other.txt')
         createLink(file, target)
         assert file.exists() && file.file
 
@@ -39,8 +38,8 @@ class BaseDirFileResolverSpec extends Specification {
 
     @Requires(TestPrecondition.SYMLINKS)
     def "normalizes absolute path which points to a relative link"() {
-        def target = createFile(new File(tmpDir.dir, 'target.txt'))
-        def file = new File(tmpDir.dir, 'a/other.txt')
+        def target = createFile(new File(tmpDir.testDirectory, 'target.txt'))
+        def file = new File(tmpDir.testDirectory, 'a/other.txt')
         createLink(file, '../target.txt')
         assert file.exists() && file.file
 
@@ -50,8 +49,8 @@ class BaseDirFileResolverSpec extends Specification {
 
     @Requires(TestPrecondition.CASE_INSENSITIVE_FS)
     def "normalizes absolute path which has mismatched case"() {
-        def file = createFile(new File(tmpDir.dir, 'dir/file.txt'))
-        def path = new File(tmpDir.dir, 'dir/FILE.txt')
+        def file = createFile(new File(tmpDir.testDirectory, 'dir/file.txt'))
+        def path = new File(tmpDir.testDirectory, 'dir/FILE.txt')
         assert path.exists() && path.file
 
         expect:
@@ -60,10 +59,10 @@ class BaseDirFileResolverSpec extends Specification {
 
     @Requires([TestPrecondition.SYMLINKS, TestPrecondition.CASE_INSENSITIVE_FS])
     def "normalizes absolute path which points to a target using mismatched case"() {
-        def target = createFile(new File(tmpDir.dir, 'target.txt'))
-        def file = new File(tmpDir.dir, 'dir/file.txt')
+        def target = createFile(new File(tmpDir.testDirectory, 'target.txt'))
+        def file = new File(tmpDir.testDirectory, 'dir/file.txt')
         createLink(file, target)
-        def path = new File(tmpDir.dir, 'dir/FILE.txt')
+        def path = new File(tmpDir.testDirectory, 'dir/FILE.txt')
         assert path.exists() && path.file
 
         expect:
@@ -72,7 +71,7 @@ class BaseDirFileResolverSpec extends Specification {
 
     @Requires(TestPrecondition.SYMLINKS)
     def "normalizes path which points to a link to something that does not exist"() {
-        def file = new File(tmpDir.dir, 'a/other.txt')
+        def file = new File(tmpDir.testDirectory, 'a/other.txt')
         createLink(file, 'unknown.txt')
         assert !file.exists() && !file.file
 
@@ -82,8 +81,8 @@ class BaseDirFileResolverSpec extends Specification {
 
     @Requires(TestPrecondition.SYMLINKS)
     def "normalizes path when ancestor is an absolute link"() {
-        def target = createFile(new File(tmpDir.dir, 'target/file.txt'))
-        def file = new File(tmpDir.dir, 'a/b/file.txt')
+        def target = createFile(new File(tmpDir.testDirectory, 'target/file.txt'))
+        def file = new File(tmpDir.testDirectory, 'a/b/file.txt')
         createLink(file.parentFile, target.parentFile)
         assert file.exists() && file.file
 
@@ -93,8 +92,8 @@ class BaseDirFileResolverSpec extends Specification {
 
     @Requires(TestPrecondition.CASE_INSENSITIVE_FS)
     def "normalizes path when ancestor has mismatched case"() {
-        def file = createFile(new File(tmpDir.dir, "a/b/file.txt"))
-        def path = new File(tmpDir.dir, "A/b/file.txt")
+        def file = createFile(new File(tmpDir.testDirectory, "a/b/file.txt"))
+        def path = new File(tmpDir.testDirectory, "A/b/file.txt")
         assert file.exists() && file.file
 
         expect:
@@ -104,15 +103,15 @@ class BaseDirFileResolverSpec extends Specification {
     @Requires(TestPrecondition.CASE_INSENSITIVE_FS)
     def "normalizes ancestor with mismatched case when target file does not exist"() {
         tmpDir.createDir("a")
-        def file = new File(tmpDir.dir, "a/b/file.txt")
-        def path = new File(tmpDir.dir, "A/b/file.txt")
+        def file = new File(tmpDir.testDirectory, "a/b/file.txt")
+        def path = new File(tmpDir.testDirectory, "A/b/file.txt")
 
         expect:
         normalize(path) == file
     }
 
     def "normalizes relative path"() {
-        def ancestor = new File(tmpDir.dir, "test")
+        def ancestor = new File(tmpDir.testDirectory, "test")
         def baseDir = new File(ancestor, "base")
         def sibling = new File(ancestor, "sub")
         def child = createFile(new File(baseDir, "a/b/file.txt"))
@@ -129,8 +128,8 @@ class BaseDirFileResolverSpec extends Specification {
 
     @Requires(TestPrecondition.SYMLINKS)
     def "normalizes relative path when base dir is a link"() {
-        def target = createFile(new File(tmpDir.dir, 'target/file.txt'))
-        def baseDir = new File(tmpDir.dir, 'base')
+        def target = createFile(new File(tmpDir.testDirectory, 'target/file.txt'))
+        def baseDir = new File(tmpDir.testDirectory, 'base')
         createLink(baseDir, "target")
         def file = new File(baseDir, 'file.txt')
         assert file.exists() && file.file
@@ -141,8 +140,8 @@ class BaseDirFileResolverSpec extends Specification {
 
     @Requires(TestPrecondition.WINDOWS)
     def "normalizes path which uses windows 8.3 name"() {
-        def file = createFile(new File(tmpDir.dir, 'dir/file-with-long-name.txt'))
-        def path = new File(tmpDir.dir, 'dir/FILE-W~1.TXT')
+        def file = createFile(new File(tmpDir.testDirectory, 'dir/file-with-long-name.txt'))
+        def path = new File(tmpDir.testDirectory, 'dir/FILE-W~1.TXT')
         assert path.exists() && path.file
 
         expect:
@@ -188,7 +187,7 @@ class BaseDirFileResolverSpec extends Specification {
         file
     }
 
-    def normalize(Object path, File baseDir = tmpDir.dir) {
+    def normalize(Object path, File baseDir = tmpDir.testDirectory) {
         new BaseDirFileResolver(FileSystems.default, baseDir).resolve(path)
     }
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverTest.groovy
index 8202960..10115b1 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverTest.groovy
@@ -20,9 +20,9 @@ import org.gradle.api.PathValidation
 import org.gradle.api.file.FileCollection
 import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection
 import org.gradle.internal.nativeplatform.filesystem.FileSystems
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.PreconditionVerifier
 import org.gradle.util.Requires
-import org.gradle.util.TemporaryFolder
 import org.gradle.util.TestPrecondition
 import org.junit.Before
 import org.junit.Rule
@@ -44,11 +44,11 @@ class BaseDirFileResolverTest {
     File testDir
 
     BaseDirFileResolver baseDirConverter
-    @Rule public TemporaryFolder rootDir = new TemporaryFolder()
+    @Rule public TestNameTestDirectoryProvider rootDir = new TestNameTestDirectoryProvider()
     @Rule public PreconditionVerifier preconditions = new PreconditionVerifier()
 
     @Before public void setUp() {
-        baseDir = rootDir.dir
+        baseDir = rootDir.testDirectory
         baseDirConverter = new BaseDirFileResolver(FileSystems.default, baseDir)
         testFile = new File(baseDir, 'testfile')
         testDir = new File(baseDir, 'testdir')
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileOperationsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileOperationsTest.groovy
index eabc636..43c5019 100755
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileOperationsTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileOperationsTest.groovy
@@ -32,9 +32,9 @@ import org.gradle.api.internal.tasks.TaskResolver
 import org.gradle.internal.os.OperatingSystem
 import org.gradle.process.ExecResult
 import org.gradle.process.internal.ExecException
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.ClasspathUtil
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
 import org.junit.Rule
 import org.junit.Test
 import spock.lang.Specification
@@ -45,7 +45,7 @@ public class DefaultFileOperationsTest extends Specification {
     private final TemporaryFileProvider temporaryFileProvider = Mock()
     private DefaultFileOperations fileOperations = new DefaultFileOperations(resolver, taskResolver, temporaryFileProvider)
     @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder()
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
     def resolvesFile() {
         when:
@@ -165,7 +165,7 @@ public class DefaultFileOperationsTest extends Specification {
 //        resolver.resolveFilesAsTree(['file'] as Object[]) >> fileTree
 //        resolver.resolveFilesAsTree(['file'] as Set) >> fileTree
         fileTree.matching(_) >> fileTree
-        resolver.resolve('dir') >> tmpDir.getDir()
+        resolver.resolve('dir') >> tmpDir.getTestDirectory()
 
         when:
         def result = fileOperations.copy { from 'file'; into 'dir' }
@@ -291,7 +291,7 @@ public class DefaultFileOperationsTest extends Specification {
         when:
         ExecResult result = fileOperations.exec {
             executable = "touch"
-            workingDir = tmpDir.getDir()
+            workingDir = tmpDir.getTestDirectory()
             args testFile.name
         }
 
@@ -309,8 +309,8 @@ public class DefaultFileOperationsTest extends Specification {
         when:
         fileOperations.exec {
             executable = "touch"
-            workingDir = tmpDir.getDir()
-            args tmpDir.dir.name + "/nonExistingDir/someFile"
+            workingDir = tmpDir.getTestDirectory()
+            args tmpDir.testDirectory.name + "/nonExistingDir/someFile"
         }
 
         then:
@@ -327,8 +327,8 @@ public class DefaultFileOperationsTest extends Specification {
         ExecResult result = fileOperations.exec {
             ignoreExitValue = true
             executable = "touch"
-            workingDir = tmpDir.getDir()
-            args tmpDir.dir.name + "/nonExistingDir/someFile"
+            workingDir = tmpDir.getTestDirectory()
+            args tmpDir.testDirectory.name + "/nonExistingDir/someFile"
         }
 
         then:
@@ -336,7 +336,7 @@ public class DefaultFileOperationsTest extends Specification {
     }
 
     def resolver() {
-        return TestFiles.resolver(tmpDir.testDir)
+        return TestFiles.resolver(tmpDir.testDirectory)
     }
 }
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileTreeElementTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileTreeElementTest.groovy
index a0b9ee2..6907615 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileTreeElementTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileTreeElementTest.groovy
@@ -16,16 +16,15 @@
 package org.gradle.api.internal.file
 
 import org.gradle.api.file.FileTreeElement
+import org.gradle.internal.nativeplatform.filesystem.FileSystems
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.Requires
-import org.gradle.util.TemporaryFolder
 import org.gradle.util.TestPrecondition
-import org.gradle.internal.nativeplatform.filesystem.FileSystems
-
 import org.junit.Rule
 import spock.lang.Specification
 
 class DefaultFileTreeElementTest extends Specification {
-    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
     @Requires(TestPrecondition.FILE_PERMISSIONS)
     def "permissions on file can be read"() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultSourceDirectorySetTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultSourceDirectorySetTest.groovy
index 29338cf..c069291 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultSourceDirectorySetTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultSourceDirectorySetTest.groovy
@@ -19,17 +19,18 @@ import org.gradle.api.InvalidUserDataException
 import org.gradle.api.file.FileTree
 import org.gradle.api.file.SourceDirectorySet
 import org.gradle.api.tasks.StopExecutionException
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.GFileUtils
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
 import org.junit.Rule
 import spock.lang.Specification
+
 import static org.gradle.api.tasks.AntBuilderAwareUtil.assertSetContainsForAllTypes
 import static org.hamcrest.Matchers.equalTo
 
 public class DefaultSourceDirectorySetTest extends Specification {
-    @Rule public TemporaryFolder tmpDir = new TemporaryFolder()
-    private final TestFile testDir = tmpDir.dir
+    @Rule public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    private final TestFile testDir = tmpDir.testDirectory
     private FileResolver resolver
     private DefaultSourceDirectorySet set
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProviderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProviderTest.groovy
index 74201e5..72c60e7 100755
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProviderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProviderTest.groovy
@@ -15,18 +15,17 @@
  */
 package org.gradle.api.internal.file
 
-import org.gradle.util.TemporaryFolder
+import org.gradle.internal.Factory
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
-
 import spock.lang.Specification
-import org.gradle.internal.Factory
 
 class DefaultTemporaryFileProviderTest extends Specification {
-    @Rule TemporaryFolder tmpDir
+    @Rule TestNameTestDirectoryProvider tmpDir
     DefaultTemporaryFileProvider provider
 
     def setup() {
-        provider = new DefaultTemporaryFileProvider({tmpDir.dir} as Factory)
+        provider = new DefaultTemporaryFileProvider({tmpDir.testDirectory} as Factory)
     }
 
     def "allocates temp file"() {
@@ -60,6 +59,6 @@ class DefaultTemporaryFileProviderTest extends Specification {
         assert file.exists()
         assert file.name.startsWith("prefix")
         assert file.name.endsWith("suffix")
-        assert file.path.startsWith(new File(tmpDir.dir, "foo/bar").path)
+        assert file.path.startsWith(new File(tmpDir.testDirectory, "foo/bar").path)
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/FileOrUriNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/FileOrUriNotationParserTest.groovy
index 68d4242..651ce84 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/FileOrUriNotationParserTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/FileOrUriNotationParserTest.groovy
@@ -17,13 +17,13 @@
 package org.gradle.api.internal.file
 
 import org.gradle.internal.nativeplatform.filesystem.FileSystems
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 class FileOrUriNotationParserTest extends Specification {
 
-    @Rule public TemporaryFolder folder = new TemporaryFolder();
+    @Rule public TestNameTestDirectoryProvider folder = new TestNameTestDirectoryProvider();
 
     final FileOrUriNotationParser<Serializable> parser = new FileOrUriNotationParser<Serializable>(FileSystems.default)
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitorTest.java
index 0654315..89628b1 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitorTest.java
@@ -15,8 +15,6 @@
  */
 package org.gradle.api.internal.file.archive;
 
-import java.util.Map;
-import java.util.HashMap;
 import org.apache.commons.io.IOUtils;
 import org.gradle.api.GradleException;
 import org.gradle.api.file.FileVisitDetails;
@@ -27,8 +25,8 @@ import org.gradle.api.internal.file.archive.compression.Compressor;
 import org.gradle.api.internal.file.archive.compression.GzipArchiver;
 import org.gradle.api.internal.file.archive.compression.SimpleCompressor;
 import org.gradle.api.internal.file.copy.ReadableCopySpec;
-import org.gradle.util.TemporaryFolder;
-import org.gradle.util.TestFile;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.hamcrest.Description;
 import org.jmock.Expectations;
 import org.jmock.api.Action;
@@ -40,6 +38,8 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
 
 import static org.gradle.api.file.FileVisitorUtil.assertVisitsPermissions;
 import static org.hamcrest.Matchers.*;
@@ -49,7 +49,7 @@ import static org.junit.Assert.fail;
 @RunWith(JMock.class)
 public class TarCopySpecVisitorTest {
     @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private final JUnit4Mockery context = new JUnit4Mockery();
     private final TarCopyAction copyAction = context.mock(TarCopyAction.class);
     private final ReadableCopySpec copySpec = context.mock(ReadableCopySpec.class);
@@ -57,7 +57,7 @@ public class TarCopySpecVisitorTest {
 
     @Test
     public void createsTarFile() {
-        final TestFile tarFile = initializeTarFile(tmpDir.getDir().file("test.tar"),
+        final TestFile tarFile = initializeTarFile(tmpDir.getTestDirectory().file("test.tar"),
             new SimpleCompressor());
         tarAndUntarAndCheckFileContents(tarFile);
     }
@@ -65,7 +65,7 @@ public class TarCopySpecVisitorTest {
     private void tarAndUntarAndCheckFileContents(TestFile tarFile) {
         tar(file("dir/file1"), file("file2"));
 
-        TestFile expandDir = tmpDir.getDir().file("expanded");
+        TestFile expandDir = tmpDir.getTestDirectory().file("expanded");
         tarFile.untarTo(expandDir);
         expandDir.file("dir/file1").assertContents(equalTo("contents of dir/file1"));
         expandDir.file("file2").assertContents(equalTo("contents of file2"));
@@ -73,21 +73,21 @@ public class TarCopySpecVisitorTest {
 
     @Test
     public void createsGzipCompressedTarFile() {
-        final TestFile tarFile = initializeTarFile(tmpDir.getDir().file("test.tgz"),
+        final TestFile tarFile = initializeTarFile(tmpDir.getTestDirectory().file("test.tgz"),
             GzipArchiver.getCompressor());
         tarAndUntarAndCheckFileContents(tarFile);
     }
 
     @Test
     public void createsBzip2CompressedTarFile() {
-        final TestFile tarFile = initializeTarFile(tmpDir.getDir().file("test.tbz2"),
+        final TestFile tarFile = initializeTarFile(tmpDir.getTestDirectory().file("test.tbz2"),
             Bzip2Archiver.getCompressor());
         tarAndUntarAndCheckFileContents(tarFile);
     }
 
     @Test
     public void tarFileContainsExpectedPermissions() {
-        final TestFile tarFile = initializeTarFile(tmpDir.getDir().file("test.tar"),
+        final TestFile tarFile = initializeTarFile(tmpDir.getTestDirectory().file("test.tar"),
             new SimpleCompressor());
 
         tar(dir("dir"), file("file"));
@@ -115,7 +115,7 @@ public class TarCopySpecVisitorTest {
 
     @Test
     public void wrapsFailureToAddElement() {
-        final TestFile tarFile = initializeTarFile(tmpDir.getDir().file("test.tar"),
+        final TestFile tarFile = initializeTarFile(tmpDir.getTestDirectory().file("test.tar"),
             new SimpleCompressor());
 
         visitor.startVisit(copyAction);
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarFileTreeTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarFileTreeTest.java
index f4b7663..ac45a8b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarFileTreeTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarFileTreeTest.java
@@ -15,23 +15,21 @@
  */
 package org.gradle.api.internal.file.archive;
 
-import java.util.Map;
-import java.util.HashMap;
-
 import org.gradle.api.GradleException;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.internal.file.FileResource;
 import org.gradle.api.internal.file.MaybeCompressedFileResource;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.Resources;
-import org.gradle.util.TemporaryFolder;
-import org.gradle.util.TestFile;
 import org.junit.Rule;
 import org.junit.Test;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import static java.util.Collections.EMPTY_LIST;
-import static org.gradle.api.file.FileVisitorUtil.assertCanStopVisiting;
-import static org.gradle.api.file.FileVisitorUtil.assertVisits;
-import static org.gradle.api.file.FileVisitorUtil.assertVisitsPermissions;
+import static org.gradle.api.file.FileVisitorUtil.*;
 import static org.gradle.api.tasks.AntBuilderAwareUtil.assertSetContainsForAllTypes;
 import static org.gradle.util.WrapUtil.toList;
 import static org.hamcrest.Matchers.containsString;
@@ -40,11 +38,11 @@ import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 
 public class TarFileTreeTest {
-    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder();
+    @Rule public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     @Rule public final Resources resources = new Resources();
-    private final TestFile tarFile = tmpDir.getDir().file("test.tar");
-    private final TestFile rootDir = tmpDir.getDir().file("root");
-    private final TestFile expandDir = tmpDir.getDir().file("tmp");
+    private final TestFile tarFile = tmpDir.getTestDirectory().file("test.tar");
+    private final TestFile rootDir = tmpDir.getTestDirectory().file("root");
+    private final TestFile expandDir = tmpDir.getTestDirectory().file("tmp");
     private final TarFileTree tree = new TarFileTree(new MaybeCompressedFileResource(new FileResource(tarFile)), expandDir);
 
     @Test
@@ -64,7 +62,7 @@ public class TarFileTreeTest {
 
     @Test
     public void readsGzippedTarFile() {
-        TestFile tgz = tmpDir.getDir().file("test.tgz");
+        TestFile tgz = tmpDir.getTestDirectory().file("test.tgz");
 
         rootDir.file("subdir/file1.txt").write("content");
         rootDir.file("subdir2/file2.txt").write("content");
@@ -78,7 +76,7 @@ public class TarFileTreeTest {
 
     @Test
     public void readsBzippedTarFile() {
-        TestFile tbz2 = tmpDir.getDir().file("test.tbz2");
+        TestFile tbz2 = tmpDir.getTestDirectory().file("test.tbz2");
 
         rootDir.file("subdir/file1.txt").write("content");
         rootDir.file("subdir2/file2.txt").write("content");
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitorTest.java
index ef16626..9684b52 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitorTest.java
@@ -15,16 +15,14 @@
  */
 package org.gradle.api.internal.file.archive;
 
-import java.util.Map;
-import java.util.HashMap;
 import org.apache.commons.io.IOUtils;
 import org.gradle.api.GradleException;
 import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.file.RelativePath;
 import org.gradle.api.internal.file.copy.ArchiveCopyAction;
 import org.gradle.api.internal.file.copy.ReadableCopySpec;
-import org.gradle.util.TestFile;
-import org.gradle.util.TemporaryFolder;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.hamcrest.Description;
 import org.jmock.Expectations;
 import org.jmock.api.Action;
@@ -37,15 +35,18 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
 
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
 import static org.gradle.api.file.FileVisitorUtil.assertVisitsPermissions;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
 
 @RunWith(JMock.class)
 public class ZipCopySpecVisitorTest {
     @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private final JUnit4Mockery context = new JUnit4Mockery();
     private final ArchiveCopyAction copyAction = context.mock(ArchiveCopyAction.class);
     private final ReadableCopySpec copySpec = context.mock(ReadableCopySpec.class);
@@ -54,7 +55,7 @@ public class ZipCopySpecVisitorTest {
 
     @Before
     public void setup() {
-        zipFile = tmpDir.getDir().file("test.zip");
+        zipFile = tmpDir.getTestDirectory().file("test.zip");
         context.checking(new Expectations(){{
             allowing(copyAction).getArchivePath();
             will(returnValue(zipFile));
@@ -65,7 +66,7 @@ public class ZipCopySpecVisitorTest {
     public void createsZipFile() {
         zip(dir("dir"), file("dir/file1"), file("file2"));
 
-        TestFile expandDir = tmpDir.getDir().file("expanded");
+        TestFile expandDir = tmpDir.getTestDirectory().file("expanded");
         zipFile.unzipTo(expandDir);
         expandDir.file("dir/file1").assertContents(equalTo("contents of dir/file1"));
         expandDir.file("file2").assertContents(equalTo("contents of file2"));
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipFileTreeTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipFileTreeTest.java
index eab06e8..fb324e6 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipFileTreeTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipFileTreeTest.java
@@ -15,30 +15,31 @@
  */
 package org.gradle.api.internal.file.archive;
 
-import java.util.Map;
-import java.util.HashMap;
-
-import org.gradle.util.Resources;
-import org.gradle.util.TemporaryFolder;
-import org.gradle.util.TestFile;
-import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.GradleException;
-import org.junit.Test;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
+import org.gradle.util.Resources;
 import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
 
-import static org.gradle.util.WrapUtil.*;
-import static org.gradle.api.tasks.AntBuilderAwareUtil.*;
+import static java.util.Collections.EMPTY_LIST;
 import static org.gradle.api.file.FileVisitorUtil.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-import static java.util.Collections.*;
+import static org.gradle.api.tasks.AntBuilderAwareUtil.assertSetContainsForAllTypes;
+import static org.gradle.util.WrapUtil.toList;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
 
 public class ZipFileTreeTest {
-    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder();
+    @Rule public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     @Rule public final Resources resources = new Resources();
-    private final TestFile zipFile = tmpDir.getDir().file("test.zip");
-    private final TestFile rootDir = tmpDir.getDir().file("root");
-    private final TestFile expandDir = tmpDir.getDir().file("tmp");
+    private final TestFile zipFile = tmpDir.getTestDirectory().file("test.zip");
+    private final TestFile rootDir = tmpDir.getTestDirectory().file("root");
+    private final TestFile expandDir = tmpDir.getTestDirectory().file("tmp");
     private final ZipFileTree tree = new ZipFileTree(zipFile, expandDir);
 
     @Test
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileCollectionTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileCollectionTest.java
index 93bd3da..ecaa42b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileCollectionTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileCollectionTest.java
@@ -21,9 +21,9 @@ import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.tasks.TaskResolver;
 import org.gradle.api.tasks.TaskDependency;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.HelperUtil;
 import org.gradle.util.JUnit4GroovyMockery;
-import org.gradle.util.TemporaryFolder;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -46,7 +46,7 @@ import static org.junit.Assert.assertThat;
 public class DefaultConfigurableFileCollectionTest {
     private final JUnit4Mockery context = new JUnit4GroovyMockery();
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private final FileResolver resolverMock = context.mock(FileResolver.class);
     private final TaskResolver taskResolverStub = context.mock(TaskResolver.class);
     private final DefaultConfigurableFileCollection collection = new DefaultConfigurableFileCollection(resolverMock,
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileTreeTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileTreeTest.groovy
index 4ffd00b..0dcb1ea 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileTreeTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultConfigurableFileTreeTest.groovy
@@ -26,13 +26,14 @@ import org.gradle.api.tasks.TaskDependency
 import org.gradle.api.tasks.util.AbstractTestForPatternSet
 import org.gradle.api.tasks.util.PatternFilterable
 import org.gradle.api.tasks.util.PatternSet
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.TemporaryFolder
 import org.gradle.util.WrapUtil
 import org.jmock.integration.junit4.JUnit4Mockery
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+
 import static org.gradle.api.file.FileVisitorUtil.assertCanStopVisiting
 import static org.gradle.api.file.FileVisitorUtil.assertVisits
 import static org.gradle.api.tasks.AntBuilderAwareUtil.assertSetContainsForAllTypes
@@ -48,8 +49,8 @@ class DefaultConfigurableFileTreeTest extends AbstractTestForPatternSet {
     TaskResolver taskResolverStub = context.mock(TaskResolver.class);
     DefaultConfigurableFileTree fileSet
     FileResolver fileResolverStub = [resolve: {it as File}] as FileResolver
-    @Rule public TemporaryFolder tmpDir = new TemporaryFolder();
-    File testDir = tmpDir.dir
+    @Rule public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
+    File testDir = tmpDir.testDirectory
 
     PatternFilterable getPatternSet() {
         return fileSet
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DirectoryFileTreeTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DirectoryFileTreeTest.java
index 892d893..9cb0f03 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DirectoryFileTreeTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DirectoryFileTreeTest.java
@@ -20,9 +20,9 @@ import org.gradle.api.file.RelativePath;
 import org.gradle.api.internal.file.copy.CopySpecVisitor;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.JUnit4GroovyMockery;
-import org.gradle.util.TemporaryFolder;
-import org.gradle.util.TestFile;
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
@@ -49,7 +49,7 @@ import static org.junit.Assert.*;
 @RunWith(JMock.class)
 public class DirectoryFileTreeTest {
     @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private JUnit4Mockery context = new JUnit4GroovyMockery();
     private CopySpecVisitor visitor;
 
@@ -163,7 +163,7 @@ public class DirectoryFileTreeTest {
             inSequence(visiting);
         }});
 
-        DirectoryFileTree fileTree = new DirectoryFileTree(root.getMock()).depthFirst();
+        DirectoryFileTree fileTree = new DirectoryFileTree(root.getMock()).postfix();
         fileTree.visit(visitor);
     }
 
@@ -253,17 +253,17 @@ public class DirectoryFileTreeTest {
 
     @Test
     public void hasUsefulDisplayName() {
-        DirectoryFileTree treeWithNoIncludesOrExcludes = new DirectoryFileTree(tmpDir.getDir());
+        DirectoryFileTree treeWithNoIncludesOrExcludes = new DirectoryFileTree(tmpDir.getTestDirectory());
         PatternSet includesOnly = new PatternSet();
         includesOnly.include("a/b", "c");
-        DirectoryFileTree treeWithIncludes = new DirectoryFileTree(tmpDir.getDir(), includesOnly);
+        DirectoryFileTree treeWithIncludes = new DirectoryFileTree(tmpDir.getTestDirectory(), includesOnly);
         PatternSet excludesOnly = new PatternSet();
         excludesOnly.exclude("a/b", "c");
-        DirectoryFileTree treeWithExcludes = new DirectoryFileTree(tmpDir.getDir(), excludesOnly);
+        DirectoryFileTree treeWithExcludes = new DirectoryFileTree(tmpDir.getTestDirectory(), excludesOnly);
 
-        assertThat(treeWithNoIncludesOrExcludes.getDisplayName(), equalTo(String.format("directory '%s'", tmpDir.getDir())));
-        assertThat(treeWithIncludes.getDisplayName(), equalTo(String.format("directory '%s' include 'a/b', 'c'", tmpDir.getDir())));
-        assertThat(treeWithExcludes.getDisplayName(), equalTo(String.format("directory '%s' exclude 'a/b', 'c'", tmpDir.getDir())));
+        assertThat(treeWithNoIncludesOrExcludes.getDisplayName(), equalTo(String.format("directory '%s'", tmpDir.getTestDirectory())));
+        assertThat(treeWithIncludes.getDisplayName(), equalTo(String.format("directory '%s' include 'a/b', 'c'", tmpDir.getTestDirectory())));
+        assertThat(treeWithExcludes.getDisplayName(), equalTo(String.format("directory '%s' exclude 'a/b', 'c'", tmpDir.getTestDirectory())));
     }
 
     private Action stopVisiting() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/MapFileTreeTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/MapFileTreeTest.java
index 92e4809..9eb4a54 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/MapFileTreeTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/MapFileTreeTest.java
@@ -16,23 +16,24 @@
 package org.gradle.api.internal.file.collections;
 
 import groovy.lang.Closure;
-import static org.gradle.api.file.FileVisitorUtil.*;
-import static org.gradle.api.tasks.AntBuilderAwareUtil.*;
-import org.gradle.util.TestFile;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.HelperUtil;
-import org.gradle.util.TemporaryFolder;
-import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.*;
-
 import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.List;
 
+import static org.gradle.api.file.FileVisitorUtil.assertCanStopVisiting;
+import static org.gradle.api.file.FileVisitorUtil.assertVisits;
+import static org.gradle.api.tasks.AntBuilderAwareUtil.assertSetContainsForAllTypes;
+import static org.gradle.util.WrapUtil.toList;
+import static org.hamcrest.Matchers.equalTo;
+
 public class MapFileTreeTest {
     @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
-    private TestFile rootDir = tmpDir.getDir();
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
+    private TestFile rootDir = tmpDir.getTestDirectory();
     private final MapFileTree tree = new MapFileTree(rootDir);
 
     @Test
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/SingleIncludePatternFileTreeSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/SingleIncludePatternFileTreeSpec.groovy
new file mode 100644
index 0000000..e979a39
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/SingleIncludePatternFileTreeSpec.groovy
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.collections
+
+import org.gradle.api.file.FileVisitDetails
+import org.gradle.api.file.FileVisitor
+import org.gradle.api.specs.Spec
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.ClassRule
+import spock.lang.Shared
+import spock.lang.Specification
+
+class SingleIncludePatternFileTreeSpec extends Specification {
+    @Shared @ClassRule TestNameTestDirectoryProvider tempDir
+
+    def visitor = Mock(FileVisitor)
+
+    def fileTree
+
+    def setupSpec() {
+        tempDir.testDirectory.create {
+            dir1 {
+                file("file1")
+                file("file2")
+                file("file3")
+            }
+
+            dir2 {
+                file("file1")
+                file("file2")
+                file("file3")
+
+                dir1 {
+                    file("file1")
+                    file("file2")
+                    file("file3")
+                }
+
+                dir2 {}
+            }
+
+            dir3 {
+                file("file1")
+                file("file2")
+                file("file3")
+            }
+        }
+    }
+
+    def "include leaf file"() {
+        fileTree = new SingleIncludePatternFileTree(tempDir.testDirectory, "dir1/file2")
+        def expectedDir = tempDir.file("dir1")
+        def expectedFile = tempDir.file("dir1/file2")
+
+        when:
+        fileTree.visit(visitor)
+
+        then:
+        1 * visitor.visitDir(_) >> { FileVisitDetails details ->
+            with(details) {
+                file == expectedDir
+                directory
+                size == expectedDir.size()
+                name == expectedDir.name
+                path == "dir1"
+                relativePath.segments == ["dir1"]
+            }
+        }
+
+        then:
+        1 * visitor.visitFile(_) >> { FileVisitDetails details ->
+            with(details) {
+                file == expectedFile
+                !directory
+                size == expectedFile.size()
+                name == expectedFile.name
+                path == "dir1/file2"
+                relativePath.segments == ["dir1", "file2"]
+            }
+        }
+
+        then:
+        0 * _
+    }
+
+    def "include inner file"() {
+        fileTree = new SingleIncludePatternFileTree(tempDir.testDirectory, "dir2/file2")
+
+        when:
+        fileTree.visit(visitor)
+
+        then:
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/file2") })
+        0 * _
+    }
+
+    def "include leaf dir"() {
+        fileTree = new SingleIncludePatternFileTree(tempDir.testDirectory, "dir2/dir2")
+
+        when:
+        fileTree.visit(visitor)
+
+        then:
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2/dir2") })
+        0 * _
+    }
+
+    def "include inner dir"() {
+        fileTree = new SingleIncludePatternFileTree(tempDir.testDirectory, "dir2")
+
+        when:
+        fileTree.visit(visitor)
+
+        then:
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2") })
+
+        then:
+        0 * _
+    }
+
+    def "include directory non-recursively"() {
+        fileTree = new SingleIncludePatternFileTree(tempDir.testDirectory, "dir2/*")
+
+        when:
+        fileTree.visit(visitor)
+
+        then:
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/file1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/file2") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/file3") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2/dir1") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2/dir2") })
+        0 * _
+    }
+
+    def "include directory recursively"() {
+        fileTree = new SingleIncludePatternFileTree(tempDir.testDirectory, includePattern)
+
+        when:
+        fileTree.visit(visitor)
+
+        then:
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/file1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/file2") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/file3") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2/dir1") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2/dir2") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/dir1/file1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/dir1/file2") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/dir1/file3") })
+        0 * _
+
+        where:
+        includePattern << ["dir2/**", "dir2/"]
+    }
+
+    def "find all file1's"() {
+        fileTree = new SingleIncludePatternFileTree(tempDir.testDirectory, "**/file1")
+
+        when:
+        fileTree.visit(visitor)
+
+        then:
+        1 * visitor.visitDir({ it.file == tempDir.file("dir1") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir3") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2/dir1") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2/dir2") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir1/file1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/file1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir3/file1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/dir1/file1") })
+        0 * _
+    }
+
+    def "find all file1's under dir2"() {
+        fileTree = new SingleIncludePatternFileTree(tempDir.testDirectory, "dir2/**/file1")
+
+        when:
+        fileTree.visit(visitor)
+
+        then:
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2/dir1") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2/dir2") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/file1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/dir1/file1") })
+        0 * _
+    }
+
+    def "include everything"() {
+        fileTree = new SingleIncludePatternFileTree(tempDir.testDirectory, "**")
+
+        when:
+        fileTree.visit(visitor)
+
+        then:
+        1 * visitor.visitDir({ it.file == tempDir.file("dir1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir1/file1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir1/file2") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir1/file3") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/file1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/file2") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/file3") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir3") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir3/file1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir3/file2") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir3/file3") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2/dir1") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2/dir2") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/dir1/file1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/dir1/file2") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/dir1/file3") })
+        0 * _
+    }
+
+    def "inner *"() {
+        fileTree = new SingleIncludePatternFileTree(tempDir.testDirectory, "*/file1")
+
+        when:
+        fileTree.visit(visitor)
+
+        then:
+        1 * visitor.visitDir({ it.file == tempDir.file("dir1") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir3") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir1/file1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/file1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir3/file1") })
+        0 * _
+    }
+
+    def "inner ?"() {
+        fileTree = new SingleIncludePatternFileTree(tempDir.testDirectory, "dir?/file1")
+
+        when:
+        fileTree.visit(visitor)
+
+        then:
+        1 * visitor.visitDir({ it.file == tempDir.file("dir1") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir3") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir1/file1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/file1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir3/file1") })
+        0 * _
+    }
+
+    def "stop visiting"() {
+        fileTree = new SingleIncludePatternFileTree(tempDir.testDirectory, "dir?/file1")
+
+        when:
+        fileTree.visit(visitor)
+
+        then:
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2") }) >> { FileVisitDetails details -> details.stopVisiting() }
+        0 * visitor.visitDir({ it.path.startsWith("dir2") })
+        0 * visitor.visitFile({ it.path.startsWith("dir2") })
+    }
+
+    def "use exclude spec"() {
+        fileTree = new SingleIncludePatternFileTree(tempDir.testDirectory, "dir?/file1", { it.path.startsWith("dir2") } as Spec)
+
+        when:
+        fileTree.visit(visitor)
+
+        then:
+        1 * visitor.visitDir({ it.file == tempDir.file("dir1") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir3") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir1/file1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir3/file1") })
+        0 * _
+    }
+
+    def "use backslashes"() {
+        fileTree = new SingleIncludePatternFileTree(tempDir.testDirectory, "dir?\\file1")
+
+        when:
+        fileTree.visit(visitor)
+
+        then:
+        1 * visitor.visitDir({ it.file == tempDir.file("dir1") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir2") })
+        1 * visitor.visitDir({ it.file == tempDir.file("dir3") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir1/file1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir2/file1") })
+        1 * visitor.visitFile({ it.file == tempDir.file("dir3/file1") })
+        0 * _
+    }
+
+    def "display name"() {
+        fileTree = new SingleIncludePatternFileTree(tempDir.testDirectory, "dir?/file1")
+
+        expect:
+        fileTree.displayName == "directory '$tempDir.testDirectory' include 'dir?/file1'"
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecImplTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecImplTest.groovy
index 6a2a41d..089d334 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecImplTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecImplTest.groovy
@@ -23,21 +23,22 @@ import org.gradle.api.file.RelativePath
 import org.gradle.api.internal.file.FileResolver
 import org.gradle.api.specs.Spec
 import org.gradle.api.tasks.util.PatternSet
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
 import org.jmock.integration.junit4.JMock
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 
 @RunWith(JMock)
 public class CopySpecImplTest {
 
-    @Rule public TemporaryFolder testDir = new TemporaryFolder();
-    private TestFile baseFile = testDir.dir
+    @Rule public TestNameTestDirectoryProvider testDir = new TestNameTestDirectoryProvider();
+    private TestFile baseFile = testDir.testDirectory
     private final JUnit4GroovyMockery context = new JUnit4GroovyMockery();
     private final FileResolver fileResolver = context.mock(FileResolver);
     private final CopySpecImpl spec = new CopySpecImpl(fileResolver)
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/DeleteActionImplTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/DeleteActionImplTest.groovy
index 2004394..b5f5439 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/DeleteActionImplTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/DeleteActionImplTest.groovy
@@ -16,20 +16,21 @@
 package org.gradle.api.internal.file.copy
 
 import org.gradle.api.internal.file.TestFiles
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
+
 /**
  * @author Hans Dockter
  */
 class DeleteActionImplTest extends Specification {
     @Rule
-    TemporaryFolder tmpDir = new TemporaryFolder()
-    DeleteActionImpl delete = new DeleteActionImpl(TestFiles.resolver(tmpDir.dir))
+    TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    DeleteActionImpl delete = new DeleteActionImpl(TestFiles.resolver(tmpDir.testDirectory))
 
     def deletesDirectory() {
-        TestFile dir = tmpDir.getDir();
+        TestFile dir = tmpDir.getTestDirectory();
         dir.file("somefile").createFile();
 
         when:
@@ -41,7 +42,7 @@ class DeleteActionImplTest extends Specification {
     }
 
     def deletesFile() {
-        TestFile dir = tmpDir.getDir();
+        TestFile dir = tmpDir.getTestDirectory();
         TestFile file = dir.file("somefile");
         file.createFile();
 
@@ -54,7 +55,7 @@ class DeleteActionImplTest extends Specification {
     }
 
     def deletesFileByPath() {
-        TestFile dir = tmpDir.getDir();
+        TestFile dir = tmpDir.getTestDirectory();
         TestFile file = dir.file("somefile");
         file.createFile();
 
@@ -67,8 +68,8 @@ class DeleteActionImplTest extends Specification {
     }
 
     def deletesMultipleTargets() {
-        TestFile file = tmpDir.getDir().file("somefile").createFile();
-        TestFile dir = tmpDir.getDir().file("somedir").createDir();
+        TestFile file = tmpDir.getTestDirectory().file("somefile").createFile();
+        TestFile dir = tmpDir.getTestDirectory().file("somedir").createDir();
         dir.file("sub/child").createFile();
 
         when:
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopySpecVisitorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopySpecVisitorTest.java
index 8f1f1a1..06345eb 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopySpecVisitorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopySpecVisitorTest.java
@@ -19,7 +19,7 @@ package org.gradle.api.internal.file.copy;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.file.RelativePath;
-import org.gradle.util.TemporaryFolder;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -32,7 +32,8 @@ import java.io.File;
 import java.io.IOException;
 
 import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
 
 @RunWith(JMock.class)
 public class FileCopySpecVisitorTest {
@@ -40,11 +41,11 @@ public class FileCopySpecVisitorTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
     private final FileCopySpecVisitor visitor = new FileCopySpecVisitor();
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
     @Before
     public void setUp() throws IOException {
-        destDir = tmpDir.getDir().file("dest");
+        destDir = tmpDir.getTestDirectory().file("dest");
     }
 
     @Test
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitorTest.java
index db488eb..6ddd161 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitorTest.java
@@ -20,9 +20,9 @@ import org.gradle.api.file.FileCopyDetails;
 import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.file.RelativePath;
 import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.HelperUtil;
-import org.gradle.util.TemporaryFolder;
-import org.gradle.util.TestFile;
 import org.hamcrest.Description;
 import org.jmock.Expectations;
 import org.jmock.Sequence;
@@ -48,7 +48,7 @@ public class MappingCopySpecVisitorTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
 
     @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private final CopySpecVisitor delegate = context.mock(CopySpecVisitor.class);
     private final ReadableCopySpec spec = context.mock(ReadableCopySpec.class);
     private final FileVisitDetails details = context.mock(FileVisitDetails.class);
@@ -219,7 +219,7 @@ public class MappingCopySpecVisitorTest {
 
         mappedDetails.filter(HelperUtil.toClosure("{ 'PREFIX: ' + it } "));
 
-        TestFile destDir = tmpDir.getDir().file("test.txt");
+        TestFile destDir = tmpDir.getTestDirectory().file("test.txt");
         mappedDetails.copyTo(destDir);
         destDir.assertContents(equalTo("PREFIX: content"));
     }
@@ -227,7 +227,7 @@ public class MappingCopySpecVisitorTest {
     @Test
     public void explicitFileModeDefinitionIsAppliedToTarget() throws IOException {
         final FileCopyDetails mappedDetails = expectActionExecutedWhenFileVisited();
-        final TestFile destFile = tmpDir.getDir().file("test.txt").createFile();
+        final TestFile destFile = tmpDir.getTestDirectory().file("test.txt").createFile();
 
         // set file permissions explicitly
         mappedDetails.setMode(0645);
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitorTest.java
index eb04b6b..68e806b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitorTest.java
@@ -19,8 +19,8 @@ import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.file.FileVisitor;
 import org.gradle.api.file.RelativePath;
 import org.gradle.api.internal.file.collections.DirectoryFileTree;
-import org.gradle.util.TemporaryFolder;
-import org.gradle.util.TestFile;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -40,7 +40,7 @@ import static org.junit.Assert.assertTrue;
 @RunWith(JMock.class)
 public class SyncCopySpecVisitorTest {
     @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private final JUnit4Mockery context = new JUnit4Mockery();
     private final CopySpecVisitor delegate = context.mock(CopySpecVisitor.class);
     private final SyncCopySpecVisitor visitor = new SyncCopySpecVisitor(delegate);
@@ -88,7 +88,7 @@ public class SyncCopySpecVisitorTest {
         assert visited.contains(new RelativePath(true, "included.txt"));
         assert !visited.contains(new RelativePath(true, "extra", "extra.txt"));
         final Set<RelativePath> actual = new HashSet<RelativePath>();
-        new DirectoryFileTree(destDir).depthFirst().visit(new FileVisitor() {
+        new DirectoryFileTree(destDir).postfix().visit(new FileVisitor() {
             public void visitDir(FileVisitDetails dirDetails) {
             }
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/PathKeyFileStoreTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/PathKeyFileStoreTest.groovy
index c4cc453..3dfbc15 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/PathKeyFileStoreTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/PathKeyFileStoreTest.groovy
@@ -18,14 +18,14 @@ package org.gradle.api.internal.filestore
 
 import org.gradle.api.Action
 import org.gradle.api.GradleException
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 class PathKeyFileStoreTest extends Specification {
 
-    @Rule TemporaryFolder dir = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider dir = new TestNameTestDirectoryProvider()
     TestFile fsBase
     PathKeyFileStore store
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/PathNormalisingKeyFileStoreTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/PathNormalisingKeyFileStoreTest.groovy
index cbe5916..b02bdeb 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/PathNormalisingKeyFileStoreTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/PathNormalisingKeyFileStoreTest.groovy
@@ -16,15 +16,15 @@
 
 package org.gradle.api.internal.filestore
 
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
+import org.gradle.api.Action
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
-import org.gradle.api.Action
 
 class PathNormalisingKeyFileStoreTest extends Specification {
 
-    @Rule TemporaryFolder dir = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider dir = new TestNameTestDirectoryProvider()
     TestFile fsBase
     PathNormalisingKeyFileStore store
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/UniquePathKeyFileStoreTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/UniquePathKeyFileStoreTest.groovy
index 92c4bab..c1ec9cb 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/UniquePathKeyFileStoreTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/UniquePathKeyFileStoreTest.groovy
@@ -17,13 +17,13 @@
 package org.gradle.api.internal.filestore
 
 import org.gradle.api.Action
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 class UniquePathKeyFileStoreTest extends Specification {
 
-    @Rule TemporaryFolder temporaryFolder = new TemporaryFolder();
+    @Rule TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider();
     Action<File> action = Mock()
 
     UniquePathKeyFileStore uniquePathKeyFileStore
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/CharSequenceNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/CharSequenceNotationParserTest.groovy
new file mode 100644
index 0000000..c60fee4
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/CharSequenceNotationParserTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.notations.parsers
+
+import spock.lang.Specification
+
+class CharSequenceNotationParserTest extends Specification {
+    def parser = new CharSequenceNotationParser()
+
+    def "handles Strings"() {
+        expect:
+        converts("abc", "abc")
+    }
+
+    def "handles GStrings"() {
+        expect:
+        def foo = "abc"
+        converts("$foo", "abc")
+    }
+
+    def "handles StringBuilders"() {
+        def builder = new StringBuilder()
+        builder.append("abc")
+
+        expect:
+        converts(builder, "abc")
+    }
+
+    void converts(from, to) {
+        assert parser.parseNotation(from).getClass() == String
+        assert parser.parseNotation(from) == to
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ClosureToSpecNotationParserSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ClosureToSpecNotationParserSpec.groovy
deleted file mode 100644
index 9b75e9f..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ClosureToSpecNotationParserSpec.groovy
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.notations.parsers
-
-import org.gradle.api.internal.notations.api.UnsupportedNotationException
-import spock.lang.Specification
-
-/**
- * by Szczepan Faber, created at: 10/12/12
- */
-class ClosureToSpecNotationParserSpec extends Specification {
-
-    private ClosureToSpecNotationParser parser = new ClosureToSpecNotationParser()
-
-    def "converts closures"() {
-        expect:
-        parser.parseNotation({ it == 'foo' }).isSatisfiedBy("foo")
-        !parser.parseNotation({ it == 'foo' }).isSatisfiedBy("bar")
-
-        when:
-        parser.parseNotation("oups")
-
-        then:
-        thrown(UnsupportedNotationException)
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ClosureToSpecNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ClosureToSpecNotationParserTest.groovy
new file mode 100644
index 0000000..fbd7659
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ClosureToSpecNotationParserTest.groovy
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.notations.parsers
+
+import org.gradle.api.internal.notations.api.UnsupportedNotationException
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 10/12/12
+ */
+class ClosureToSpecNotationParserTest extends Specification {
+
+    private ClosureToSpecNotationParser parser = new ClosureToSpecNotationParser()
+
+    def "converts closures"() {
+        expect:
+        parser.parseNotation({ it == 'foo' }).isSatisfiedBy("foo")
+        !parser.parseNotation({ it == 'foo' }).isSatisfiedBy("bar")
+
+        when:
+        parser.parseNotation("oups")
+
+        then:
+        thrown(UnsupportedNotationException)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistryTest.groovy
index e542623..9d5f7f4 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistryTest.groovy
@@ -24,14 +24,14 @@ import org.gradle.api.plugins.PluginInstantiationException
 import org.gradle.api.plugins.UnknownPluginException
 import org.gradle.internal.reflect.Instantiator
 import org.gradle.internal.reflect.ObjectInstantiationException
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.GUtil
-import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
 
 class DefaultPluginRegistryTest extends Specification {
     @Rule
-    final TemporaryFolder testDir = new TemporaryFolder()
+    final TestNameTestDirectoryProvider testDir = new TestNameTestDirectoryProvider()
     final Instantiator instantiator = Mock()
     final ClassLoader classLoader = Mock()
     private DefaultPluginRegistry pluginRegistry = new DefaultPluginRegistry(classLoader, instantiator)
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy
index efae86b..2ddf1e4 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy
@@ -16,6 +16,8 @@
 
 package org.gradle.api.internal.project
 
+import org.gradle.api.component.SoftwareComponentContainer
+
 import java.awt.Point
 import java.text.FieldPosition
 import org.apache.tools.ant.types.FileSet
@@ -102,6 +104,7 @@ class DefaultProjectTest {
     ProcessOperations processOperationsMock = context.mock(ProcessOperations)
     LoggingManagerInternal loggingManagerMock = context.mock(LoggingManagerInternal.class)
     Instantiator instantiatorMock = context.mock(Instantiator)
+    SoftwareComponentContainer softwareComponentsMock = context.mock(SoftwareComponentContainer.class)
 
     @Before
     void setUp() {
@@ -132,6 +135,7 @@ class DefaultProjectTest {
             allowing(serviceRegistryMock).get(ConfigurationContainerInternal); will(returnValue(configurationContainerMock))
             allowing(serviceRegistryMock).get(ArtifactHandler); will(returnValue(context.mock(ArtifactHandler)))
             allowing(serviceRegistryMock).get(DependencyHandler); will(returnValue(dependencyHandlerMock))
+            allowing(serviceRegistryMock).get(SoftwareComponentContainer); will(returnValue(softwareComponentsMock))
             allowing(serviceRegistryMock).get(ProjectEvaluator); will(returnValue(projectEvaluator))
             allowing(serviceRegistryMock).getFactory(AntBuilder); will(returnValue(antBuilderFactoryMock))
             allowing(serviceRegistryMock).get(PluginContainer); will(returnValue(pluginContainerMock))
@@ -228,6 +232,7 @@ class DefaultProjectTest {
         assertSame(repositoryHandlerMock, project.repositories)
         assert projectRegistry.is(project.projectRegistry)
         assertFalse project.state.executed
+        assert project.components.is(softwareComponentsMock)
     }
 
     @Test public void testNullVersionAndStatus() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.java
index 2a16ae5..077e2b9 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.java
@@ -24,11 +24,11 @@ import org.gradle.api.internal.GradleInternal;
 import org.gradle.groovy.scripts.StringScriptSource;
 import org.gradle.groovy.scripts.UriScriptSource;
 import org.gradle.internal.Factory;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.testfixtures.internal.GlobalTestServices;
 import org.gradle.testfixtures.internal.TestTopLevelBuildServiceRegistry;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.gradle.util.MultiParentClassLoader;
-import org.gradle.util.TemporaryFolder;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -51,8 +51,8 @@ public class ProjectFactoryTest {
     private final JUnit4Mockery context = new JUnit4GroovyMockery();
     private final MultiParentClassLoader buildScriptClassLoader = new MultiParentClassLoader(getClass().getClassLoader());
     @Rule
-    public TemporaryFolder testDir = new TemporaryFolder();
-    private final File rootDir = testDir.getDir();
+    public TestNameTestDirectoryProvider testDir = new TestNameTestDirectoryProvider();
+    private final File rootDir = testDir.getTestDirectory();
     private final File projectDir = new File(rootDir, "project");
     private Factory<RepositoryHandler> repositoryHandlerFactory = context.mock(Factory.class);
     private RepositoryHandler repositoryHandler = context.mock(RepositoryHandler.class);
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java
index e2d5431..6b721e5 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java
@@ -74,7 +74,7 @@ public class ProjectInternalServiceRegistryTest {
     private final PluginRegistry pluginRegistry = context.mock(PluginRegistry.class);
     private final DependencyResolutionServices dependencyResolutionServices = context.mock(DependencyResolutionServices.class);
     private final RepositoryHandler repositoryHandler = context.mock(RepositoryHandler.class);
-    private final Factory publishServicesFactory = context.mock(Factory.class);
+    private final ArtifactPublicationServices publicationServices = context.mock(ArtifactPublicationServices.class);
     private final DependencyHandler dependencyHandler = context.mock(DependencyHandler.class);
     private final ArtifactHandler artifactHandler = context.mock(ArtifactHandler.class);
     private final DirectInstantiator instantiator = new DirectInstantiator();
@@ -139,8 +139,7 @@ public class ProjectInternalServiceRegistryTest {
     public void providesAnArtifactPublicationServicesFactory() {
         expectDependencyResolutionServicesCreated();
 
-        assertThat(registry.getFactory(ArtifactPublicationServices.class), sameInstance(publishServicesFactory));
-        assertThat(registry.getFactory(ArtifactPublicationServices.class), sameInstance(registry.getFactory(ArtifactPublicationServices.class)));
+        assertThat(registry.get(ArtifactPublicationServices.class), sameInstance(publicationServices));
     }
 
     @Test
@@ -258,8 +257,8 @@ public class ProjectInternalServiceRegistryTest {
             allowing(dependencyResolutionServices).getResolveRepositoryHandler();
             will(returnValue(repositoryHandler));
 
-            allowing(dependencyResolutionServices).getPublishServicesFactory();
-            will(returnValue(publishServicesFactory));
+            allowing(dependencyResolutionServices).createArtifactPublicationServices();
+            will(returnValue(publicationServices));
 
             allowing(dependencyResolutionServices).getConfigurationContainer();
             will(returnValue(configurationContainer));
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistryTest.java
index cc90720..1844816 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistryTest.java
@@ -16,14 +16,15 @@
 
 package org.gradle.api.internal.project;
 
-import org.gradle.internal.Factory;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.TaskOutputsInternal;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.tasks.DefaultTaskInputs;
 import org.gradle.api.internal.tasks.DefaultTaskOutputs;
+import org.gradle.api.internal.tasks.TaskStatusNagger;
 import org.gradle.api.logging.LoggingManager;
 import org.gradle.api.tasks.TaskInputs;
+import org.gradle.internal.Factory;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.logging.LoggingManagerInternal;
 import org.jmock.Expectations;
@@ -33,6 +34,7 @@ import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import static junit.framework.Assert.assertSame;
 import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.Assert.assertThat;
@@ -64,13 +66,19 @@ public class TaskInternalServiceRegistryTest {
         TaskOutputsInternal outputs = registry.get(TaskOutputsInternal.class);
         assertThat(outputs, instanceOf(DefaultTaskOutputs.class));
     }
-    
+
+    @Test
+    public void createsATaskStatusNaggerInstance() {
+        TaskStatusNagger nagger = registry.get(TaskStatusNagger.class);
+        assertSame(nagger, registry.get(TaskStatusNagger.class));
+    }
+
     @Test
     public void createsALoggingManagerAndStdOutputCapture() {
         final Factory<LoggingManagerInternal> loggingManagerFactory = context.mock(Factory.class);
         final LoggingManager loggingManager = context.mock(LoggingManagerInternal.class);
 
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             allowing(parent).getFactory(LoggingManagerInternal.class);
             will(returnValue(loggingManagerFactory));
             one(loggingManagerFactory).create();
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistryTest.groovy
index 8612aac..6cc5a13 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistryTest.groovy
@@ -17,6 +17,7 @@
 package org.gradle.api.internal.project
 
 import org.gradle.StartParameter
+import org.gradle.api.internal.*
 import org.gradle.api.internal.classpath.DefaultModuleRegistry
 import org.gradle.api.internal.classpath.ModuleRegistry
 import org.gradle.api.internal.classpath.PluginModuleRegistry
@@ -29,6 +30,7 @@ import org.gradle.configuration.DefaultScriptPluginFactory
 import org.gradle.configuration.ScriptPluginFactory
 import org.gradle.groovy.scripts.DefaultScriptCompilerFactory
 import org.gradle.groovy.scripts.ScriptCompilerFactory
+import org.gradle.initialization.*
 import org.gradle.internal.Factory
 import org.gradle.internal.concurrent.DefaultExecutorFactory
 import org.gradle.internal.concurrent.ExecutorFactory
@@ -41,14 +43,12 @@ import org.gradle.messaging.remote.MessagingServer
 import org.gradle.process.internal.DefaultWorkerProcessFactory
 import org.gradle.process.internal.WorkerProcessBuilder
 import org.gradle.profile.ProfileEventAdapter
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.ClassLoaderFactory
 import org.gradle.util.MultiParentClassLoader
-import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
 import spock.lang.Timeout
-import org.gradle.api.internal.*
-import org.gradle.initialization.*
 
 import static org.hamcrest.Matchers.instanceOf
 import static org.hamcrest.Matchers.sameInstance
@@ -56,7 +56,7 @@ import static org.junit.Assert.assertThat
 
 public class TopLevelBuildServiceRegistryTest extends Specification {
     @Rule
-    TemporaryFolder tmpDir = new TemporaryFolder()
+    TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     StartParameter startParameter = new StartParameter()
     ServiceRegistry parent = Mock()
     Factory<CacheFactory> cacheFactoryFactory = Mock()
@@ -66,7 +66,7 @@ public class TopLevelBuildServiceRegistryTest extends Specification {
     TopLevelBuildServiceRegistry registry = new TopLevelBuildServiceRegistry(parent, startParameter)
 
     def setup() {
-        startParameter.gradleUserHomeDir = tmpDir.dir
+        startParameter.gradleUserHomeDir = tmpDir.testDirectory
         parent.getFactory(CacheFactory) >> cacheFactoryFactory
         cacheFactoryFactory.create() >> cacheFactory
         parent.get(ClassLoaderRegistry) >> classLoaderRegistry
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactoryTest.java
index e4f5840..032e97d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactoryTest.java
@@ -24,7 +24,11 @@ import org.gradle.api.internal.AbstractTask;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.DefaultProject;
 import org.gradle.api.tasks.*;
-import org.gradle.util.*;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.ReflectionUtil;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -52,8 +56,8 @@ public class AnnotationProcessingTaskFactoryTest {
     private final ITaskFactory delegate = context.mock(ITaskFactory.class);
     private final Map args = new HashMap();
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
-    private final TestFile testDir = tmpDir.getDir();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
+    private final TestFile testDir = tmpDir.getTestDirectory();
     private final File existingFile = testDir.file("file.txt").touch();
     private final File missingFile = testDir.file("missing.txt");
     private final TestFile existingDir = testDir.file("dir").createDir();
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/resource/UriResourceTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/resource/UriResourceTest.groovy
index a1fa172..dc2fb84 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/resource/UriResourceTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/resource/UriResourceTest.groovy
@@ -17,12 +17,13 @@
 
 package org.gradle.api.internal.resource
 
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.testing.internal.util.Network
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+
 import static org.hamcrest.Matchers.equalTo
 import static org.hamcrest.Matchers.nullValue
 import static org.junit.Assert.*
@@ -32,7 +33,7 @@ class UriResourceTest {
     private File file;
     private URI fileUri;
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
     @Before
     public void setUp() throws URISyntaxException {
@@ -42,7 +43,7 @@ class UriResourceTest {
     }
 
     private URI createJar() throws URISyntaxException {
-        TestFile jarFile = tmpDir.dir.file('test.jar');
+        TestFile jarFile = tmpDir.testDirectory.file('test.jar');
         testDir.file('ignoreme').write('content');
         testDir.zipTo(jarFile);
         return new URI("jar:${jarFile.toURI()}!/build.script")
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java
index 41d9a07..872c390 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java
@@ -23,20 +23,23 @@ import org.gradle.api.UnknownTaskException;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.project.taskfactory.ITaskFactory;
+import org.gradle.api.tasks.TaskDependency;
 import org.gradle.util.GUtil;
 import org.gradle.util.HelperUtil;
 import org.jmock.Expectations;
+import org.jmock.api.Invocation;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.action.CustomAction;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Collections;
 import java.util.Map;
 
 import static java.util.Collections.singletonMap;
 import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.*;
 
 @RunWith(JMock.class)
 public class DefaultTaskContainerTest {
@@ -260,6 +263,40 @@ public class DefaultTaskContainerTest {
         expectTaskLookupInOtherProject(":", "task", task);
         assertThat(container.resolveTask(new StringBuilder(":task")), sameInstance(task));
     }
+
+    @Test public void actualizing() {
+        final Task task = addTask("a");
+        context.checking(new Expectations(){{
+            allowing(task).dependsOn("b");
+            allowing(task).getTaskDependencies();
+            TaskDependency aTaskDependency = context.mock(TaskDependency.class);
+            will(returnValue(aTaskDependency));
+
+            final Task b = task("b");
+            one(taskFactory).createTask(singletonMap(Task.TASK_NAME, "b"));
+            will(returnValue(b));
+
+            TaskDependency bTaskDependency = context.mock(TaskDependency.class, "bTaskDependency");
+            allowing(b).getTaskDependencies();
+            will(returnValue(bTaskDependency));
+            allowing(bTaskDependency).getDependencies(b);
+            will(returnValue(Collections.emptySet()));
+
+            allowing(aTaskDependency).getDependencies(task);
+            will(new CustomAction("create task b") {
+                public Object invoke(Invocation invocation) throws Throwable {
+                    container.add("b");
+                    return Collections.singleton(b);
+                }
+            });
+
+        }});
+        task.dependsOn("b");
+
+        assertEquals(1, container.size());
+        container.actualize();
+        assertEquals(2, container.size());
+    }
     
     private void expectTaskLookupInOtherProject(final String projectPath, final String taskName, final Task task) {
         context.checking(new Expectations() {{
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskInputsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskInputsTest.groovy
index 2f4280a..8aba401 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskInputsTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskInputsTest.groovy
@@ -15,13 +15,14 @@
  */
 package org.gradle.api.internal.tasks
 
-import java.util.concurrent.Callable
 import org.gradle.api.file.FileCollection
 import org.gradle.api.file.FileTree
 import org.gradle.api.internal.TaskInternal
 import org.gradle.api.internal.file.FileResolver
 import spock.lang.Specification
 
+import java.util.concurrent.Callable
+
 class DefaultTaskInputsTest extends Specification {
     private final File treeFile = new File('tree')
     private final FileTree tree = [getFiles: { [treeFile] as Set}] as FileTree
@@ -29,7 +30,9 @@ class DefaultTaskInputsTest extends Specification {
             resolve: {new File(it)},
             resolveFilesAsTree: {tree}
     ] as FileResolver
-    private final DefaultTaskInputs inputs = new DefaultTaskInputs(resolver, {} as TaskInternal)
+
+    private TaskStatusNagger taskStatusNagger = Mock()
+    private final DefaultTaskInputs inputs = new DefaultTaskInputs(resolver, {} as TaskInternal, taskStatusNagger)
 
     def defaultValues() {
         expect:
@@ -170,7 +173,7 @@ class DefaultTaskInputsTest extends Specification {
         inputs.hasInputs
         inputs.hasSourceFiles
     }
-    
+
     def hasInputsWhenSourceFilesRegistered() {
         when:
         inputs.source('a')
@@ -179,4 +182,57 @@ class DefaultTaskInputsTest extends Specification {
         inputs.hasInputs
         inputs.hasSourceFiles
     }
+
+    public void callsTaskStatusNaggerWhenFileMethodCalled() {
+        when:
+        inputs.file("aFile")
+        then:
+        1 * taskStatusNagger.nagIfTaskNotInConfigurableState("TaskInputs.file(Object)")
+    }
+
+    public void callsTaskStatusNaggerWhenFilesMethodCalled() {
+        when:
+        inputs.files("aFile", "bFile")
+        then:
+        1 * taskStatusNagger.nagIfTaskNotInConfigurableState("TaskInputs.files(Object...)")
+    }
+
+    public void callsTaskStatusNaggerWhenDirMethodCalled() {
+        when:
+        inputs.dir("aFile")
+        then:
+        1 * taskStatusNagger.nagIfTaskNotInConfigurableState("TaskInputs.dir(Object)")
+    }
+
+    public void callsTaskStatusNaggerWhenSourceDirMethodCalled() {
+        when:
+        inputs.sourceDir("aFile")
+        then:
+        1 * taskStatusNagger.nagIfTaskNotInConfigurableState("TaskInputs.sourceDir(Object)")
+    }
+
+    public void callsTaskStatusNaggerWhenSourceMethodCalled() {
+        when:
+        inputs.source("aFile")
+        then:
+        1 * taskStatusNagger.nagIfTaskNotInConfigurableState("TaskInputs.source(Object)")
+        when:
+        inputs.source("aFile", "bFile")
+        then:
+        1 * taskStatusNagger.nagIfTaskNotInConfigurableState("TaskInputs.source(Object...)")
+    }
+
+    public void callsTaskStatusNaggerWhenPropertyMethodCalled() {
+        when:
+        inputs.property("name", "value")
+        then:
+        1 * taskStatusNagger.nagIfTaskNotInConfigurableState("TaskInputs.property(String, Object)")
+    }
+
+    public void callsTaskStatusNaggerWhenPropertiesMethodCalled() {
+        when:
+        inputs.properties(name:"value")
+        then:
+        1 * taskStatusNagger.nagIfTaskNotInConfigurableState("TaskInputs.properties(Map)")
+    }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskOutputsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskOutputsTest.groovy
index 881ad4c..25f3587 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskOutputsTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskOutputsTest.groovy
@@ -15,15 +15,17 @@
  */
 package org.gradle.api.internal.tasks
 
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.TaskExecutionHistory
 import org.gradle.api.internal.TaskInternal
 import org.gradle.api.internal.file.FileResolver
 import spock.lang.Specification
-import org.gradle.api.internal.TaskExecutionHistory
-import org.gradle.api.file.FileCollection
 
 class DefaultTaskOutputsTest extends Specification {
+
+    private TaskStatusNagger taskStatusNagger = Mock()
     private final TaskInternal task = [toString: {'task'}] as TaskInternal
-    private final DefaultTaskOutputs outputs = new DefaultTaskOutputs({new File(it)} as FileResolver, task)
+    private final DefaultTaskOutputs outputs = new DefaultTaskOutputs({new File(it)} as FileResolver, task, taskStatusNagger)
 
     public void hasNoOutputsByDefault() {
         setup:
@@ -51,7 +53,7 @@ class DefaultTaskOutputsTest extends Specification {
         then:
         outputs.hasOutput
     }
-    
+
     public void hasOutputsWhenNonEmptyOutputFilesRegistered() {
         when:
         outputs.files('a')
@@ -67,7 +69,7 @@ class DefaultTaskOutputsTest extends Specification {
         then:
         outputs.hasOutput
     }
-    
+
     public void canSpecifyUpToDatePredicateUsingClosure() {
         boolean upToDate = false
 
@@ -98,7 +100,29 @@ class DefaultTaskOutputsTest extends Specification {
         f == outputFiles
         1 * history.outputFiles >> outputFiles
     }
-    
+
+    public void callsTaskStatusNaggerWhenFileMethodCalled() {
+        when:
+        outputs.file("aFile")
+        then:
+        1 * taskStatusNagger.nagIfTaskNotInConfigurableState("TaskOutputs.file(Object)")
+    }
+
+    public void callsTaskStatusNaggerWhenFilesMethodCalled() {
+        when:
+        outputs.files("aFile", "bFile")
+        then:
+        1 * taskStatusNagger.nagIfTaskNotInConfigurableState("TaskOutputs.files(Object...)")
+    }
+
+    public void callsTaskStatusNaggerWhenDirMethodCalled() {
+        when:
+        outputs.dir("aFile")
+        then:
+        1 * taskStatusNagger.nagIfTaskNotInConfigurableState("TaskOutputs.dir(Object)")
+        0 * taskStatusNagger.nagIfTaskNotInConfigurableState("TaskOutputs.files(Object...)");
+    }
+
     public void getPreviousFilesFailsWhenNoTaskHistoryAvailable() {
         when:
         outputs.previousFiles
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/TaskStateInternalTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/TaskStateInternalTest.groovy
index a22a3e2..aa3bac8 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/TaskStateInternalTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/TaskStateInternalTest.groovy
@@ -31,6 +31,7 @@ class TaskStateInternalTest {
     public void defaultValues() {
         assertFalse(state.getExecuted())
         assertFalse(state.getExecuting())
+        assertTrue(state.configurable)
         assertThat(state.getFailure(), nullValue())
         assertFalse(state.getDidWork())
         assertFalse(state.getSkipped())
@@ -42,6 +43,7 @@ class TaskStateInternalTest {
         state.executed()
         assertTrue(state.executed)
         assertFalse(state.skipped)
+        assertFalse(state.configurable)
         assertThat(state.getFailure(), nullValue())
     }
     
@@ -51,6 +53,7 @@ class TaskStateInternalTest {
         state.executed(failure)
         assertTrue(state.executed)
         assertFalse(state.skipped)
+        assertFalse(state.configurable)
         assertThat(state.failure, sameInstance(failure))
     }
 
@@ -59,6 +62,7 @@ class TaskStateInternalTest {
         state.skipped('skip-message')
         assertTrue(state.executed)
         assertTrue(state.skipped)
+        assertFalse(state.configurable)
         assertThat(state.skipMessage, equalTo('skip-message'))
     }
 
@@ -67,6 +71,7 @@ class TaskStateInternalTest {
         state.upToDate()
         assertTrue(state.executed)
         assertTrue(state.skipped)
+        assertFalse(state.configurable)
         assertThat(state.skipMessage, equalTo('UP-TO-DATE'))
     }
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuterTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuterTest.java
index 84f775d..f7bf87e 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuterTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuterTest.java
@@ -29,8 +29,10 @@ import org.gradle.logging.StandardOutputCapture;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.jmock.Expectations;
 import org.jmock.Sequence;
+import org.jmock.api.Invocation;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.action.CustomAction;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -58,7 +60,7 @@ public class ExecuteActionsTaskExecuterTest {
 
     @Before
     public void setUp() {
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             ProjectInternal project = context.mock(ProjectInternal.class);
 
             allowing(task).getProject();
@@ -137,7 +139,7 @@ public class ExecuteActionsTaskExecuterTest {
 
             one(state).executed(null);
             inSequence(sequence);
-            
+
             one(state).setExecuting(false);
             inSequence(sequence);
 
@@ -149,6 +151,50 @@ public class ExecuteActionsTaskExecuterTest {
     }
 
     @Test
+    public void executeDoesOperateOnNewActionListInstance() {
+        context.checking(new Expectations() {
+            {
+                allowing(task).getActions();
+                will(returnValue(toList(action1)));
+
+                one(listener).beforeActions(task);
+                inSequence(sequence);
+
+                one(state).setExecuting(true);
+                inSequence(sequence);
+
+                one(state).setDidWork(true);
+                inSequence(sequence);
+
+                one(standardOutputCapture).start();
+                inSequence(sequence);
+
+                one(action1).execute(task);
+                will(new CustomAction("Add action to actions list") {
+                    public Object invoke(Invocation invocation) throws Throwable {
+                        task.getActions().add(action2);
+                        return null;
+                    }
+                });
+
+                inSequence(sequence);
+
+                one(standardOutputCapture).stop();
+                one(state).executed(null);
+                inSequence(sequence);
+
+                one(state).setExecuting(false);
+                inSequence(sequence);
+
+                one(listener).afterActions(task);
+                inSequence(sequence);
+            }
+        });
+        executer.execute(task, state);
+    }
+
+
+    @Test
     public void stopsAtFirstActionWhichThrowsException() {
         final Throwable failure = new RuntimeException("failure");
         final Collector<Throwable> wrappedFailure = collector();
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/xml/SimpleXmlWriterSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/xml/SimpleXmlWriterSpec.groovy
new file mode 100644
index 0000000..38a67b4
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/xml/SimpleXmlWriterSpec.groovy
@@ -0,0 +1,417 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.xml
+
+import spock.lang.Specification
+
+import javax.xml.parsers.DocumentBuilderFactory
+import org.gradle.util.TextUtil
+
+/**
+ * by Szczepan Faber, created at: 12/3/12
+ */
+class SimpleXmlWriterSpec extends Specification {
+
+    private sw = new ByteArrayOutputStream()
+    private writer = new SimpleXmlWriter(sw)
+
+    String getXml() {
+        def text = sw.toString("UTF-8")
+        println "TEXT {$text}"
+        def document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(sw.toByteArray()))
+        assert document
+        return text
+    }
+
+    def "writes basic XML"() {
+        when:
+        writer.startElement("root").attribute("items", "9")
+        writer.startElement("item").endElement()
+        writer.startElement("item").attribute("size", "10m")
+        writer.characters("some chars")
+        writer.characters(" and some other".toCharArray())
+        writer.characters("x  chars.x".toCharArray(), 2, 7)
+        writer.startElement("foo").characters(" ")
+        writer.endElement()
+        writer.endElement()
+        writer.endElement()
+
+        then:
+        xml == '<?xml version="1.1" encoding="UTF-8"?><root items="9"><item/><item size="10m">some chars and some other chars.<foo> </foo></item></root>'
+    }
+
+    def "escapes reserved characters in text content"() {
+        when:
+        writer.startElement("root")
+        writer.characters("chars with interesting stuff: < < > ' \" ]]> \r\n \t")
+        writer.endElement()
+
+        then:
+        xml.contains('<root>chars with interesting stuff: &lt; < > \' " ]]> \r\n \t</root>')
+    }
+
+    def "escapes reserved characters in attribute values"() {
+        when:
+        writer.startElement("root")
+        writer.startElement("item").attribute("description", "encoded: \t < < > ' \n\r\"  ")
+        writer.endElement()
+        writer.endElement()
+
+        then:
+        xml.contains('<item description="encoded: 	 &lt; < > \' 

"  "/>')
+
+        and:
+        def item = new XmlSlurper().parseText(xml).item
+        item. at description.text() == "encoded: \t < < > ' \n\r\"  "
+    }
+
+    def "writes CDATA"() {
+        when:
+        writer.startElement("root")
+        writer.startElement("stuff")
+
+        writer.startCDATA()
+        writer.characters('x hey x'.toCharArray(), 2, 4)
+        writer.characters('joe'.toCharArray())
+        writer.characters("!")
+        writer.endCDATA()
+
+        writer.endElement()
+
+        writer.startCDATA()
+        writer.characters('encodes: ]]> ')
+        writer.characters('does not encode: ]] ')
+        writer.characters('html allowed: <> &')
+        writer.endCDATA()
+
+        writer.endElement()
+
+        then:
+        xml.contains('<stuff><![CDATA[hey joe!]]></stuff><![CDATA[encodes: ]]]]><![CDATA[> does not encode: ]] html allowed: <> &]]>')
+    }
+
+    def "encodes CDATA when token on the border"() {
+        when:
+        //the end token is on the border of both char arrays
+        writer.startElement('root')
+        writer.startCDATA()
+        writer.characters('stuff ]]')
+        writer.characters('> more stuff')
+        writer.endCDATA()
+        writer.endElement()
+
+        then:
+        xml.contains('<![CDATA[stuff ]]]]><![CDATA[> more stuff]]>')
+    }
+
+    def "does not encode CDATA when token separated in different CDATAs"() {
+        when:
+        //the end token is on the border of both char arrays
+
+        writer.startElement('root')
+
+        writer.startCDATA();
+        writer.characters('stuff ]]')
+        writer.endCDATA();
+
+        writer.startCDATA()
+        writer.characters('> more stuff')
+        writer.endCDATA();
+
+        writer.endElement()
+
+        then:
+        xml.contains('<root><![CDATA[stuff ]]]]><![CDATA[> more stuff]]></root>')
+    }
+
+    def "encodes non-ASCII characters"() {
+        when:
+        writer.startElement("\u0200").attribute("\u0201", "\u0202")
+        writer.characters("\u0203")
+        writer.startCDATA().characters("\u0204").endCDATA()
+        writer.endElement()
+
+        then:
+        xml.contains('<\u0200 \u0201="\u0202">\u0203<![CDATA[\u0204]]></\u0200>')
+    }
+
+    def "escapes restricted characters in text content"() {
+        when:
+        writer.startElement("root")
+        writer.attribute("name", "\u0084\u0002")
+        writer.characters("\u0084\u0002\u009f")
+        writer.startCDATA().characters("\u0084\u0002").endCDATA()
+        writer.endElement()
+
+        then:
+        xml.contains('<root name="&#x84;&#x2;">&#x84;&#x2;&#x9f;<![CDATA[]]>&#x84;<![CDATA[]]>&#x2;<![CDATA[]]></root>')
+    }
+
+    def "replaces illegal characters in text content"() {
+        given:
+
+        when:
+        writer.startElement("root")
+        writer.characters(chars)
+        writer.startElement("broken").attribute("name", chars)
+        writer.startCDATA().characters(chars).endCDATA()
+        writer.endElement()
+        writer.endElement()
+
+        then:
+        xml.contains('<root>?<broken name="?"><![CDATA[?]]></broken></root>')
+
+        where:
+        chars << ["\u0000", "\ud800", "\udfff", "\ufffe"]
+    }
+
+    def "is a Writer implementation that escapes characters"() {
+        when:
+        writer.startElement("root")
+        writer.write("some <chars>")
+        writer.write(" and ".toCharArray())
+        writer.write("x some x".toCharArray(), 2, 4)
+        writer.write(' ')
+        writer.startCDATA()
+        writer.write("cdata")
+        writer.endCDATA()
+        writer.endElement()
+
+        then:
+        xml.contains("<root>some <chars> and some <![CDATA[cdata]]></root>")
+    }
+
+    def "cannot end element when stack is empty"() {
+        writer.startElement("root")
+        writer.endElement()
+
+        when:
+        writer.endElement()
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot end element, as there are no started elements.'
+    }
+
+    def "cannot write characters when stack is empty"() {
+        when:
+        writer.characters("text")
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot write text, as there are no started elements.'
+
+        given:
+
+        when:
+        writer.startElement("root")
+        writer.endElement()
+        writer.characters("text")
+
+        then:
+        e = thrown()
+        e.message == 'Cannot write text, as there are no started elements.'
+    }
+
+    def "cannot end element when CDATA node is open"() {
+        writer.startElement("root")
+        writer.startCDATA()
+
+        when:
+        writer.endElement()
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot end element, as current CDATA node has not been closed.'
+    }
+
+    def "cannot start element when CDATA node is open"() {
+        writer.startElement("root")
+        writer.startCDATA()
+
+        when:
+        writer.startElement("nested")
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot start element, as current CDATA node has not been closed.'
+    }
+
+    def "cannot start CDATA node when CDATA node is open"() {
+        writer.startElement("root")
+        writer.startCDATA()
+
+        when:
+        writer.startCDATA()
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot start CDATA node, as current CDATA node has not been closed.'
+    }
+
+    def "cannot end CDATA node when not in a CDATA node"() {
+        writer.startElement("root")
+
+        when:
+        writer.endCDATA()
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot end CDATA node, as not currently in a CDATA node.'
+    }
+
+    def "closes tags"() {
+        when:
+        writer.startElement("root")
+        action.call(writer)
+        writer.endElement()
+
+        then:
+        sw.toString().contains("<root>") //is closed with '>'
+
+        where:
+        action << [{ it.startElement("foo"); it.endElement() },
+                { it.startCDATA(); it.endCDATA() },
+                { it.characters("bar") },
+                { it.write("close") }]
+    }
+
+    def "closes attributed tags"() {
+        when:
+        writer.startElement("root")
+        writer.attribute("foo", '115')
+        action.call(writer)
+        writer.endElement()
+
+        then:
+        sw.toString().contains('<root foo="115">') //is closed with '>'
+
+        where:
+        action << [{ it.startElement("foo"); it.endElement() },
+                { it.startCDATA(); it.endCDATA() },
+                { it.characters("bar") },
+                { it.write("close") }]
+    }
+
+    def "outputs empty element when element has no content"() {
+        when:
+        writer.startElement("root")
+        writer.startElement("empty").endElement()
+        writer.startElement("empty").attribute("property", "value").endElement()
+        writer.endElement()
+
+        then:
+        xml.contains('<root><empty/><empty property="value"/></root>')
+    }
+
+    def "writes indented XML when enabled"() {
+        sw.reset()
+        def writer = new SimpleXmlWriter(sw, "    ")
+
+        when:
+        writer.startElement("root").attribute("items", "9")
+        writer.startElement("item").endElement()
+        writer.startElement("item").characters("some text").endElement()
+        writer.startElement("item")
+        writer.startElement("nested-1")
+        writer.startElement("nested-2").characters(" ").endElement()
+        writer.endElement()
+        writer.endElement()
+        writer.startElement("item")
+        writer.startElement("thing").characters("some text").endElement()
+        writer.startElement("thing").startCDATA().characters("some text").endCDATA().endElement()
+        writer.endElement()
+        writer.startElement("mixed")
+        writer.characters("text")
+        writer.startElement("mixed-1").endElement()
+        writer.characters("text")
+        writer.startElement("mixed-2").characters("123").endElement()
+        writer.startElement("mixed-3").startElement("empty").endElement().endElement()
+        writer.characters("text")
+        writer.endElement()
+        writer.endElement()
+
+        then:
+        xml == TextUtil.toPlatformLineSeparators('''<?xml version="1.1" encoding="UTF-8"?>
+<root items="9">
+    <item/>
+    <item>some text</item>
+    <item>
+        <nested-1>
+            <nested-2> </nested-2>
+        </nested-1>
+    </item>
+    <item>
+        <thing>some text</thing>
+        <thing><![CDATA[some text]]></thing>
+    </item>
+    <mixed>text
+        <mixed-1/>text
+        <mixed-2>123</mixed-2>
+        <mixed-3>
+            <empty/>
+        </mixed-3>text</mixed>
+</root>
+''')
+    }
+
+    def "allows valid tag names"() {
+        when:
+        writer.startElement(name)
+
+        then:
+        notThrown(IllegalArgumentException)
+
+        where:
+        name << ["name", "NAME", "with-dashes", "with.dots", "with123digits", ":", "_", "\u037f\u0300"]
+    }
+
+    def "validates tag names"() {
+        when:
+        writer.startElement(name)
+
+        then:
+        def ex = thrown(IllegalArgumentException)
+        ex.message == "Invalid element name: '$name'"
+
+        where:
+        name << ["tag with space", "", "-invalid-start-char", "  ", "912", "\u00d7"]
+    }
+
+    def "allows valid attribute names"() {
+        when:
+        writer.startElement("foo").attribute(name, "foo")
+
+        then:
+        notThrown(IllegalArgumentException)
+
+        where:
+        name << ["name", "NAME", "with-dashes", "with.dots", "with123digits", ":", "_", "\u037f\u0300"]
+    }
+
+    def "validates attribute names"() {
+        when:
+        writer.startElement("foo").attribute(name, "foo")
+
+        then:
+        def ex = thrown(IllegalArgumentException)
+        ex.message == "Invalid attribute name: '$name'"
+
+        where:
+        name << ["attribute with space", "", "-invalid-start-char", "  ", "912", "\u00d7"]
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/xml/XmlTransformerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/xml/XmlTransformerTest.groovy
new file mode 100644
index 0000000..ff725bb
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/xml/XmlTransformerTest.groovy
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.xml
+
+import org.gradle.api.Action
+import org.gradle.api.XmlProvider
+import org.gradle.api.internal.DomNode
+import org.gradle.util.TextUtil
+import spock.lang.Specification
+
+class XmlTransformerTest extends Specification {
+    final XmlTransformer transformer = new XmlTransformer()
+
+    def "returns original string when no actions are provided"() {
+        expect:
+        looksLike '<root/>', transformer.transform('<root/>')
+    }
+
+    def "action can access XML as StringBuilder"() {
+        Action<XmlProvider> action = Mock()
+        transformer.addAction(action)
+
+        when:
+        def result = transformer.transform('<root/>')
+
+        then:
+        action.execute(_) >> { XmlProvider provider ->
+            def builder = provider.asString()
+            builder.insert(builder.indexOf("root"), 'some-')
+        }
+        looksLike '<some-root/>', result
+    }
+
+    def "action can access XML as Node"() {
+        Action<XmlProvider> action = Mock()
+        transformer.addAction(action)
+
+        when:
+        def result = transformer.transform('<root/>')
+
+        then:
+        action.execute(_) >> { XmlProvider provider ->
+            provider.asNode().appendNode('child1')
+        }
+        looksLike '<root>\n  <child1/>\n</root>\n', result
+    }
+
+    def "action can access XML as DOM element"() {
+        Action<XmlProvider> action = Mock()
+        transformer.addAction(action)
+
+        when:
+        def result = transformer.transform('<root/>')
+
+        then:
+        action.execute(_) >> { XmlProvider provider ->
+            def document = provider.asElement().ownerDocument
+            provider.asElement().appendChild(document.createElement('child1'))
+        }
+        looksLike '<root>\n  <child1/>\n</root>\n', result
+    }
+
+    def "can transform String to a Writer"() {
+        Action<XmlProvider> action = Mock()
+        transformer.addAction(action)
+        StringWriter writer = new StringWriter()
+
+        when:
+        transformer.transform('<root/>', writer)
+
+        then:
+        action.execute(_) >> { XmlProvider provider ->
+            provider.asNode().appendNode('child1')
+        }
+        looksLike '<root>\n  <child1/>\n</root>\n', writer.toString()
+    }
+
+    def "can transform Node to a Writer"() {
+        Action<XmlProvider> action = Mock()
+        transformer.addAction(action)
+        StringWriter writer = new StringWriter()
+        Node node = new XmlParser().parseText('<root/>')
+
+        when:
+        transformer.transform(node, writer)
+
+        then:
+        action.execute(_) >> { XmlProvider provider ->
+            provider.asNode().appendNode('child1')
+        }
+        looksLike '<root>\n  <child1/>\n</root>\n', writer.toString()
+    }
+
+    def "can use a closure as an action"() {
+        transformer.addAction { provider ->
+            provider.asNode().appendNode('child1')
+        }
+        StringWriter writer = new StringWriter()
+
+        when:
+        transformer.transform('<root/>', writer)
+
+        then:
+        looksLike '<root>\n  <child1/>\n</root>\n', writer.toString()
+    }
+
+    def "can chain actions"() {
+        Action<XmlProvider> stringAction = Mock()
+        Action<XmlProvider> nodeAction = Mock()
+        Action<XmlProvider> elementAction = Mock()
+        Action<XmlProvider> stringAction2 = Mock()
+        transformer.addAction(stringAction)
+        transformer.addAction(elementAction)
+        transformer.addAction(nodeAction)
+        transformer.addAction(stringAction2)
+
+        when:
+        def result = transformer.transform('<root/>')
+
+        then:
+        stringAction.execute(_) >> { XmlProvider provider ->
+            def builder = provider.asString()
+            builder.insert(builder.indexOf("root"), 'some-')
+        }
+        nodeAction.execute(_) >> { XmlProvider provider ->
+            provider.asNode().appendNode('child2')
+        }
+        elementAction.execute(_) >> { XmlProvider provider ->
+            def document = provider.asElement().ownerDocument
+            provider.asElement().appendChild(document.createElement('child1'))
+        }
+        stringAction2.execute(_) >> { XmlProvider provider ->
+            provider.asString().append('<!-- end -->')
+        }
+
+        looksLike '<some-root>\n  <child1/>\n  <child2/>\n</some-root>\n<!-- end -->', result
+    }
+
+    def "can chain node actions"() {
+        Action<XmlProvider> nodeAction = Mock()
+        Action<XmlProvider> nodeAction2 = Mock()
+        transformer.addAction(nodeAction)
+        transformer.addAction(nodeAction2)
+
+        when:
+        def result = transformer.transform('<root/>')
+
+        then:
+        nodeAction.execute(_) >> { XmlProvider provider ->
+            provider.asNode().appendNode('child1')
+        }
+        nodeAction2.execute(_) >> { XmlProvider provider ->
+            provider.asNode().appendNode('child2')
+        }
+        looksLike '<root>\n  <child1/>\n  <child2/>\n</root>\n', result
+    }
+
+    def "indentation correct when writing out Node"() {
+        transformer.indentation = "\t"
+        transformer.addAction { XmlProvider provider -> provider.asNode().children()[0].appendNode("grandchild") }
+
+        when:
+        def result = transformer.transform("<root>\n  <child/>\n</root>\n")
+
+        then:
+        looksLike "<root>\n\t<child>\n\t\t<grandchild/>\n\t</child>\n</root>\n", result
+    }
+
+    def "can add DOCTYPE along with nodes"() {
+        transformer.addAction { it.asNode().appendNode('someChild') }
+        transformer.addAction {
+            def s = it.asString()
+            s.insert(s.indexOf("?>") + 2, '\n<!DOCTYPE application PUBLIC "-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN" "http://java.sun.com/dtd/application_1_3.dtd">')
+        }
+
+        when:
+        def result = transformer.transform("<root></root>")
+
+        then:
+        looksLike "<!DOCTYPE application PUBLIC \"-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN\" \"http://java.sun.com/dtd/application_1_3.dtd\">\n<root>\n  <someChild/>\n</root>\n", result
+    }
+
+    def "can specify DOCTYPE when using DomNode"() {
+        StringWriter writer = new StringWriter()
+        def node = new DomNode('root')
+        node.publicId = 'public-id'
+        node.systemId = 'system-id'
+
+        when:
+        transformer.transform(node, writer)
+
+        then:
+        looksLike '''<!DOCTYPE root PUBLIC "public-id" "system-id">
+<root/>
+''', writer.toString()
+    }
+
+    def "DOCTYPE is preserved when transformed as a Node"() {
+        StringWriter writer = new StringWriter()
+        def node = new DomNode('root')
+        node.publicId = 'public-id'
+        node.systemId = 'system-id'
+        transformer.addAction { it.asNode().appendNode('someChild') }
+
+        when:
+        transformer.transform(node, writer)
+
+        then:
+        looksLike '''<!DOCTYPE root PUBLIC "public-id" "system-id">
+<root>
+  <someChild/>
+</root>
+''', writer.toString()
+    }
+
+    def "DOCTYPE is preserved when transformed as a DOM element"() {
+        StringWriter writer = new StringWriter()
+        def node = new DomNode('root')
+        node.publicId = 'public-id'
+        node.systemId = getClass().getResource("xml-transformer-test.dtd")
+        transformer.addAction { it.asElement().appendChild(it.asElement().ownerDocument.createElement('someChild')) }
+
+        when:
+        transformer.transform(node, writer)
+
+        then:
+        looksLike """<!DOCTYPE root PUBLIC "public-id" "${node.getSystemId()}">
+<root>
+  <someChild/>
+</root>
+""", writer.toString()
+    }
+
+    def "indentation correct when writing out DOM element (only) if indenting with spaces"() {
+        transformer.indentation = expected
+        transformer.addAction { XmlProvider provider ->
+            def document = provider.asElement().ownerDocument
+            document.getElementsByTagName("child").item(0).appendChild(document.createElement("grandchild"))
+        }
+
+        when:
+        def result = transformer.transform("<root>\n  <child/>\n</root>\n")
+
+        then:
+        looksLike("<root>\n$actual<child>\n$actual$actual<grandchild/>\n$actual</child>\n</root>\n", result)
+
+        where:
+        expected | actual
+        "    "   | "    "
+        "\t"     | "  " // tabs not supported, two spaces used instead
+    }
+
+    def "can use with action api"() {
+        given:
+        def writer = new StringWriter()
+        def input = "<things><thing/></things>"
+        def generator = new Action<Writer>() {
+            void execute(Writer t) {
+                t.write(input)
+            }
+        }
+
+        when:
+        transformer.transform(writer, generator)
+
+        then:
+        looksLike(input, writer.toString())
+
+        when:
+        writer.buffer.setLength(0)
+        transformer.addAction(new Action<XmlProvider>() {
+            void execute(XmlProvider xml) {
+                xml.asNode().thing[0]. at foo = "bar"
+            }
+        })
+        transformer.transform(writer, generator)
+
+        then:
+        looksLike('<things>\n  <thing foo="bar"/>\n</things>', writer.toString())
+    }
+
+    private void looksLike(String expected, String actual) {
+        assert removeTrailingWhitespace(actual) == removeTrailingWhitespace(TextUtil.toPlatformLineSeparators(addXmlDeclaration(expected)))
+    }
+
+    private String removeTrailingWhitespace(String value) {
+        return value.replaceFirst('(?s)\\s+$', "")
+    }
+
+    private String addXmlDeclaration(String value) {
+        "<?xml version=\"1.0\"?>\n" + value
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/DirectoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/DirectoryTest.groovy
index c03c076..9633666 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/DirectoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/DirectoryTest.groovy
@@ -17,6 +17,7 @@
 package org.gradle.api.tasks
 
 import org.gradle.api.InvalidUserDataException
+import org.gradle.api.UncheckedIOException
 import org.gradle.api.internal.AbstractTask
 import org.junit.Before
 import org.junit.Test
@@ -70,7 +71,7 @@ class DirectoryTest extends AbstractTaskTest {
         assert file.isFile()
     }
 
-    @Test (expected = InvalidUserDataException) public void testWithExistingFile() {
+    @Test (expected = UncheckedIOException) public void testWithExistingFile() {
         File file = new File(project.projectDir, 'testname')
         file.createNewFile()
         directory = createTask(Directory.class, project, 'testname')
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/UploadTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/UploadTest.groovy
new file mode 100644
index 0000000..8b70973
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/UploadTest.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks
+
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class UploadTest extends Specification {
+
+    def "can create task"() {
+        when:
+        HelperUtil.createTask(Upload)
+
+        then:
+        noExceptionThrown()
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/UploadTest.java b/subprojects/core/src/test/groovy/org/gradle/api/tasks/UploadTest.java
deleted file mode 100644
index 12f33c3..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/UploadTest.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright 2007 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.tasks;
-
-import groovy.lang.Closure;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.artifacts.PublishArtifactSet;
-import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.AbstractTask;
-import org.gradle.api.internal.artifacts.ArtifactPublisher;
-import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
-import org.gradle.util.ConfigureUtil;
-import org.gradle.util.HelperUtil;
-import org.jmock.Expectations;
-import org.jmock.api.Invocation;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.action.CustomAction;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.util.Collections;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class UploadTest extends AbstractTaskTest {
-    private Upload upload;
-
-    private JUnit4Mockery context = new JUnit4Mockery();
-    private RepositoryHandler repositoriesMock;
-    private ArtifactPublisher artifactPublisherMock;
-    private ConfigurationInternal configurationMock;
-    private Module moduleMock;
-
-    @Before public void setUp() {
-        upload = createTask(Upload.class);
-        repositoriesMock = context.mock(RepositoryHandler.class);
-        artifactPublisherMock = context.mock(ArtifactPublisher.class);
-        moduleMock = context.mock(Module.class);
-        configurationMock = context.mock(ConfigurationInternal.class);
-    }
-
-    public AbstractTask getTask() {
-        return upload;
-    }
-
-    @Test public void testUpload() {
-        assertThat(upload.isUploadDescriptor(), equalTo(false));
-        assertNull(upload.getDescriptorDestination());
-        assertNotNull(upload.getRepositories());
-    }
-
-    @Test public void testUploading() {
-        final File descriptorDestination = new File("somePath");
-        upload.setUploadDescriptor(true);
-        upload.setDescriptorDestination(descriptorDestination);
-        upload.setConfiguration(configurationMock);
-        upload.setArtifactPublisher(artifactPublisherMock);
-        context.checking(new Expectations() {{
-            one(configurationMock).getModule(); will(returnValue(moduleMock));
-            one(configurationMock).getHierarchy(); will(returnValue(Collections.emptySet()));
-            one(artifactPublisherMock).publish(moduleMock, Collections.<Configuration>emptySet(), descriptorDestination, null);
-        }});
-        upload.upload();
-    }
-
-    @Test public void testUploadingWithUploadDescriptorFalseAndDestinationSet() {
-        upload.setUploadDescriptor(false);
-        upload.setDescriptorDestination(new File("somePath"));
-        upload.setConfiguration(configurationMock);
-        upload.setArtifactPublisher(artifactPublisherMock);
-        context.checking(new Expectations() {{
-            one(configurationMock).getModule(); will(returnValue(moduleMock));
-            one(configurationMock).getHierarchy(); will(returnValue(Collections.emptySet()));
-            one(artifactPublisherMock).publish(moduleMock, Collections.<Configuration>emptySet(), null, null);
-        }});
-        upload.upload();
-    }
-
-    @Test public void testRepositories() {
-        upload.setRepositories(repositoriesMock);
-
-        context.checking(new Expectations(){{
-            one(repositoriesMock).configure(with(any(Closure.class)));
-            will(new CustomAction("execution configure") { 
-                public Object invoke(Invocation invocation) {
-                    return ConfigureUtil.configure((Closure)invocation.getParameter(0), invocation.getInvokedObject(), false);
-                }
-            });
-            one(repositoriesMock).mavenCentral();
-        }});
-
-        upload.repositories(HelperUtil.toClosure("{ mavenCentral() }"));
-    }
-
-    @Test public void testDeclaresConfigurationArtifactsAsInputFiles() {
-        assertThat(upload.getArtifacts(), nullValue());
-
-        upload.setConfiguration(configurationMock);
-
-        final PublishArtifactSet artifacts = context.mock(PublishArtifactSet.class);
-        final FileCollection files = context.mock(FileCollection.class);
-        context.checking(new Expectations(){{
-            one(configurationMock).getAllArtifacts();
-            will(returnValue(artifacts));
-            one(artifacts).getFiles();
-            will(returnValue(files));
-        }});
-
-        assertThat(upload.getArtifacts(), sameInstance(files));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/ant/AntTargetTest.java b/subprojects/core/src/test/groovy/org/gradle/api/tasks/ant/AntTargetTest.java
index 0c263b9..124125a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/ant/AntTargetTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/ant/AntTargetTest.java
@@ -20,25 +20,27 @@ import org.apache.tools.ant.Project;
 import org.apache.tools.ant.Target;
 import org.gradle.api.Task;
 import org.gradle.api.internal.project.DefaultProject;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.HelperUtil;
-import org.gradle.util.TemporaryFolder;
-import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
 import org.junit.Before;
-import org.junit.Test;
 import org.junit.Rule;
+import org.junit.Test;
 
 import java.io.File;
 import java.util.Set;
 
+import static org.gradle.util.WrapUtil.toSet;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
 public class AntTargetTest {
     private final Target antTarget = new Target();
     private final DefaultProject project = HelperUtil.createRootProject();
     private final AntTarget task = HelperUtil.createTask(AntTarget.class, project);
     @Rule
-    public TemporaryFolder testDir = new TemporaryFolder();
-    private final File baseDir = testDir.getDir();
+    public TestNameTestDirectoryProvider testDir = new TestNameTestDirectoryProvider();
+    private final File baseDir = testDir.getTestDirectory();
 
     @Before
     public void setUp() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/util/PatternSetTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/util/PatternSetTest.groovy
index ddba386..3df74b2 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/util/PatternSetTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/util/PatternSetTest.groovy
@@ -20,6 +20,7 @@ import org.gradle.api.file.FileTreeElement
 import org.gradle.api.file.RelativePath
 import org.gradle.api.specs.Spec
 import org.junit.Test
+import spock.lang.Issue
 
 import static org.gradle.util.Matchers.strictlyEqual
 import static org.hamcrest.Matchers.*
@@ -28,18 +29,14 @@ import static org.junit.Assert.*
 class PatternSetTest extends AbstractTestForPatternSet {
     PatternSet patternSet = new PatternSet()
 
-    PatternSet getPatternSet() {
-        patternSet
-    }
-
-    @Test public void testConstructionFromMap() {
+    @Test void testConstructionFromMap() {
         Map map = [includes: [TEST_PATTERN_1], excludes: [TEST_PATTERN_2]]
         PatternFilterable patternSet = new PatternSet(map)
         assertThat(patternSet.includes, equalTo([TEST_PATTERN_1] as Set))
         assertThat(patternSet.excludes, equalTo([TEST_PATTERN_2] as Set))
     }
 
-    @Test public void patternSetsAreEqualWhenAllPropertiesAreEqual() {
+    @Test void patternSetsAreEqualWhenAllPropertiesAreEqual() {
         assertThat(new PatternSet(), strictlyEqual(new PatternSet()))
         assertThat(new PatternSet(caseSensitive: false), strictlyEqual(new PatternSet(caseSensitive: false)))
         assertThat(new PatternSet(includes: ['i']), strictlyEqual(new PatternSet(includes: ['i'])))
@@ -53,7 +50,7 @@ class PatternSetTest extends AbstractTestForPatternSet {
         assertThat(new PatternSet(excludes: ['e']), not(equalTo(new PatternSet(excludes: ['other']))))
     }
 
-    @Test public void canCopyFromAnotherPatternSet() {
+    @Test void canCopyFromAnotherPatternSet() {
         PatternSet other = new PatternSet()
         other.include 'a', 'b'
         other.exclude 'c'
@@ -68,129 +65,122 @@ class PatternSetTest extends AbstractTestForPatternSet {
         assertThat(patternSet.excludeSpecs, equalTo(other.excludeSpecs))
     }
 
-    @Test public void createsSpecForEmptyPatternSet() {
+    @Test void createsSpecForEmptyPatternSet() {
         Spec<FileTreeElement> spec = patternSet.asSpec
 
-        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
-        assertTrue(spec.isSatisfiedBy(element(true, 'b')))
-    }
-
-    private FileTreeElement element(boolean isFile, String... elements) {
-        [
-                getRelativePath: { return new RelativePath(isFile, elements) },
-                getFile: { return new File(elements.join('/')) }
-        ] as FileTreeElement
+        assertTrue(spec.isSatisfiedBy(file('a')))
+        assertTrue(spec.isSatisfiedBy(file('b')))
     }
 
-    @Test public void createsSpecForIncludePatterns() {
+    @Test void createsSpecForIncludePatterns() {
         patternSet.include '*a*'
         patternSet.include '*b*'
         Spec<FileTreeElement> spec = patternSet.asSpec
 
-        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
-        assertTrue(spec.isSatisfiedBy(element(true, 'b')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'c')))
+        assertTrue(spec.isSatisfiedBy(file('a')))
+        assertTrue(spec.isSatisfiedBy(file('b')))
+        assertFalse(spec.isSatisfiedBy(file('c')))
     }
 
-    @Test public void createsSpecForExcludePatterns() {
+    @Test void createsSpecForExcludePatterns() {
         patternSet.exclude '*b*'
         patternSet.exclude '*c*'
         Spec<FileTreeElement> spec = patternSet.asSpec
 
-        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'b')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'c')))
+        assertTrue(spec.isSatisfiedBy(file('a')))
+        assertFalse(spec.isSatisfiedBy(file('b')))
+        assertFalse(spec.isSatisfiedBy(file('c')))
     }
 
-    @Test public void createsSpecForIncludeAndExcludePatterns() {
+    @Test void createsSpecForIncludeAndExcludePatterns() {
         patternSet.include '*a*'
         patternSet.exclude '*b*'
         Spec<FileTreeElement> spec = patternSet.asSpec
 
-        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'ab')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'ba')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'c')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'b')))
+        assertTrue(spec.isSatisfiedBy(file('a')))
+        assertFalse(spec.isSatisfiedBy(file('ab')))
+        assertFalse(spec.isSatisfiedBy(file('ba')))
+        assertFalse(spec.isSatisfiedBy(file('c')))
+        assertFalse(spec.isSatisfiedBy(file('b')))
     }
 
-    @Test public void createsSpecForIncludeSpecs() {
+    @Test void createsSpecForIncludeSpecs() {
         patternSet.include({ FileTreeElement element -> element.file.name.contains('a') } as Spec)
         Spec<FileTreeElement> spec = patternSet.asSpec
 
-        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'b')))
+        assertTrue(spec.isSatisfiedBy(file('a')))
+        assertFalse(spec.isSatisfiedBy(file('b')))
     }
 
-    @Test public void createsSpecForExcludeSpecs() {
+    @Test void createsSpecForExcludeSpecs() {
         patternSet.exclude({ FileTreeElement element -> element.file.name.contains('b') } as Spec)
         Spec<FileTreeElement> spec = patternSet.asSpec
 
-        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'b')))
+        assertTrue(spec.isSatisfiedBy(file('a')))
+        assertFalse(spec.isSatisfiedBy(file('b')))
     }
 
-    @Test public void createsSpecForIncludeAndExcludeSpecs() {
+    @Test void createsSpecForIncludeAndExcludeSpecs() {
         patternSet.include({ FileTreeElement element -> element.file.name.contains('a') } as Spec)
         patternSet.exclude({ FileTreeElement element -> element.file.name.contains('b') } as Spec)
         Spec<FileTreeElement> spec = patternSet.asSpec
 
-        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'ab')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'b')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'c')))
+        assertTrue(spec.isSatisfiedBy(file('a')))
+        assertFalse(spec.isSatisfiedBy(file('ab')))
+        assertFalse(spec.isSatisfiedBy(file('b')))
+        assertFalse(spec.isSatisfiedBy(file('c')))
     }
 
-    @Test public void createsSpecForIncludeClosure() {
+    @Test void createsSpecForIncludeClosure() {
         patternSet.include { FileTreeElement element -> element.file.name.contains('a') }
         Spec<FileTreeElement> spec = patternSet.asSpec
 
-        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'b')))
+        assertTrue(spec.isSatisfiedBy(file('a')))
+        assertFalse(spec.isSatisfiedBy(file('b')))
     }
 
-    @Test public void createsSpecForExcludeClosure() {
+    @Test void createsSpecForExcludeClosure() {
         patternSet.exclude { FileTreeElement element -> element.file.name.contains('b') }
         Spec<FileTreeElement> spec = patternSet.asSpec
 
-        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'b')))
+        assertTrue(spec.isSatisfiedBy(file('a')))
+        assertFalse(spec.isSatisfiedBy(file('b')))
     }
 
-    @Test public void createsSpecForIncludeAndExcludeClosures() {
+    @Test void createsSpecForIncludeAndExcludeClosures() {
         patternSet.include { FileTreeElement element -> element.file.name.contains('a') }
         patternSet.exclude { FileTreeElement element -> element.file.name.contains('b') }
         Spec<FileTreeElement> spec = patternSet.asSpec
 
-        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'ab')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'c')))
+        assertTrue(spec.isSatisfiedBy(file('a')))
+        assertFalse(spec.isSatisfiedBy(file('ab')))
+        assertFalse(spec.isSatisfiedBy(file('c')))
     }
 
-    @Test public void isCaseSensitiveByDefault() {
+    @Test void isCaseSensitiveByDefault() {
         patternSet.include '*a*'
         patternSet.exclude '*b*'
         Spec<FileTreeElement> spec = patternSet.asSpec
 
-        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'A')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'Ab')))
-        assertTrue(spec.isSatisfiedBy(element(true, 'aB')))
+        assertTrue(spec.isSatisfiedBy(file('a')))
+        assertFalse(spec.isSatisfiedBy(file('A')))
+        assertFalse(spec.isSatisfiedBy(file('Ab')))
+        assertTrue(spec.isSatisfiedBy(file('aB')))
     }
 
-    @Test public void createsSpecForCaseInsensitivePatternSet() {
+    @Test void createsSpecForCaseInsensitivePatternSet() {
         patternSet.include '*a*'
         patternSet.exclude '*b*'
         patternSet.caseSensitive = false
         Spec<FileTreeElement> spec = patternSet.asSpec
 
-        assertTrue(spec.isSatisfiedBy(element(true, 'A')))
-        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'AB')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'bA')))
+        assertTrue(spec.isSatisfiedBy(file('A')))
+        assertTrue(spec.isSatisfiedBy(file('a')))
+        assertFalse(spec.isSatisfiedBy(file('AB')))
+        assertFalse(spec.isSatisfiedBy(file('bA')))
     }
 
-    @Test public void createIntersectPatternSet() {
+    @Test void createIntersectPatternSet() {
         patternSet.include '*a*'
         patternSet.include { FileTreeElement element -> element.file.name.contains('1') }
         patternSet.exclude '*b*'
@@ -202,24 +192,69 @@ class PatternSetTest extends AbstractTestForPatternSet {
         intersection.exclude { FileTreeElement element -> element.file.name.contains('4') }
         Spec<FileTreeElement> spec = intersection.asSpec
 
-        assertTrue(spec.isSatisfiedBy(element(true, 'ac')))
-        assertTrue(spec.isSatisfiedBy(element(true, '13')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'a')))
-        assertFalse(spec.isSatisfiedBy(element(true, '1')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'c')))
-        assertFalse(spec.isSatisfiedBy(element(true, '3')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'acb')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'acd')))
-        assertFalse(spec.isSatisfiedBy(element(true, '132')))
-        assertFalse(spec.isSatisfiedBy(element(true, '132')))
+        assertTrue(spec.isSatisfiedBy(file('ac')))
+        assertTrue(spec.isSatisfiedBy(file('13')))
+        assertFalse(spec.isSatisfiedBy(file('a')))
+        assertFalse(spec.isSatisfiedBy(file('1')))
+        assertFalse(spec.isSatisfiedBy(file('c')))
+        assertFalse(spec.isSatisfiedBy(file('3')))
+        assertFalse(spec.isSatisfiedBy(file('acb')))
+        assertFalse(spec.isSatisfiedBy(file('acd')))
+        assertFalse(spec.isSatisfiedBy(file('132')))
+        assertFalse(spec.isSatisfiedBy(file('132')))
+    }
+
+    @Test void globalExcludes() {
+        Spec<FileTreeElement> spec = patternSet.asSpec
+
+        assertFalse(spec.isSatisfiedBy(dir('.svn')))
+        assertFalse(spec.isSatisfiedBy(file('.svn', 'abc')))
+        assertFalse(spec.isSatisfiedBy(dir('a', 'b', '.svn')))
+        assertFalse(spec.isSatisfiedBy(file('a', 'b', '.svn', 'c')))
+    }
+
+    @Issue("GRADLE-2566")
+    @Test void canUseGStringsAsIncludes() {
+        def a = "a*"
+        def b = "b*"
+
+        patternSet.includes = ["$a"]
+        patternSet.include("$b")
+
+        Spec<FileTreeElement> spec = patternSet.asSpec
+
+        assertTrue(spec.isSatisfiedBy(file("aaa")))
+        assertTrue(spec.isSatisfiedBy(file("bbb")))
+        assertFalse(spec.isSatisfiedBy(file("ccc")))
     }
 
-    @Test public void globalExcludes() {
+    @Issue("GRADLE-2566")
+    @Test void canUseGStringsAsExcludes() {
+        def a = "a"
+        def b = "b"
+
+        patternSet.excludes = ["${a}*"]
+        patternSet.exclude("${b}*")
+
         Spec<FileTreeElement> spec = patternSet.asSpec
 
-        assertFalse(spec.isSatisfiedBy(element(false, '.svn')))
-        assertFalse(spec.isSatisfiedBy(element(true, '.svn', 'abc')))
-        assertFalse(spec.isSatisfiedBy(element(false, 'a', 'b', '.svn')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'a', 'b', '.svn', 'c')))
+        assertFalse(spec.isSatisfiedBy(file("aaa")))
+        assertFalse(spec.isSatisfiedBy(file("bbb")))
+        assertTrue(spec.isSatisfiedBy(file("ccc")))
+    }
+
+    private FileTreeElement element(boolean isFile, String... elements) {
+        [
+                getRelativePath: { return new RelativePath(isFile, elements) },
+                getFile: { return new File(elements.join('/')) }
+        ] as FileTreeElement
+    }
+
+    private FileTreeElement file(String... elements) {
+        element(true, elements)
+    }
+
+    private FileTreeElement dir(String... elements) {
+        element(false, elements)
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/DefaultSerializerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/DefaultSerializerTest.groovy
deleted file mode 100644
index 70f30ca..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/cache/DefaultSerializerTest.groovy
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.cache
-
-import spock.lang.Specification
-
-class DefaultSerializerTest extends Specification {
-    def canSerializeAndDeserializeObject() {
-        GroovyClassLoader classLoader = new GroovyClassLoader(getClass().classLoader)
-        DefaultSerializer serializer = new DefaultSerializer(classLoader)
-
-        Class cl = classLoader.parseClass('package org.gradle.cache; class TestObj implements Serializable { }')
-        Object o = cl.newInstance()
-
-        when:
-        ByteArrayOutputStream outputStream = new ByteArrayOutputStream()
-        serializer.write(outputStream, o)
-        Object r = serializer.read(new ByteArrayInputStream(outputStream.toByteArray()))
-
-        then:
-        cl.isInstance(r)
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheAccessTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheAccessTest.groovy
index ae3e2f1..fa4ffc9 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheAccessTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheAccessTest.groovy
@@ -18,14 +18,14 @@ package org.gradle.cache.internal
 import org.gradle.cache.internal.btree.BTreePersistentIndexedCache
 import org.gradle.internal.Factory
 import org.gradle.messaging.serialize.Serializer
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 import static org.gradle.cache.internal.FileLockManager.LockMode.*
 
 class DefaultCacheAccessTest extends Specification {
-    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final FileLockManager lockManager = Mock()
     final File lockFile = tmpDir.file('lock.bin')
     final File targetFile = tmpDir.file('cache.bin')
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheFactoryTest.groovy
index 8d7fada..3de6d72 100755
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheFactoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheFactoryTest.groovy
@@ -17,15 +17,15 @@ package org.gradle.cache.internal
 
 import org.gradle.CacheUsage
 import org.gradle.api.Action
-import org.gradle.cache.DefaultSerializer
-import org.gradle.util.TemporaryFolder
+import org.gradle.cache.CacheValidator
+import org.gradle.messaging.serialize.DefaultSerializer
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
-import org.gradle.cache.CacheValidator
 
 class DefaultCacheFactoryTest extends Specification {
     @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder()
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final Action<?> opened = Mock()
     final Action<?> closed = Mock()
     final ProcessMetaDataProvider metaDataProvider = Mock()
@@ -53,11 +53,11 @@ class DefaultCacheFactoryTest extends Specification {
     public void "creates directory backed store instance"() {
         when:
         def factory = factoryFactory.create()
-        def cache = factory.openStore(tmpDir.dir, "<display>", FileLockManager.LockMode.Shared, null)
+        def cache = factory.openStore(tmpDir.testDirectory, "<display>", FileLockManager.LockMode.Shared, null)
 
         then:
         cache instanceof DefaultPersistentDirectoryStore
-        cache.baseDir == tmpDir.dir
+        cache.baseDir == tmpDir.testDirectory
         cache.toString().startsWith "<display>"
 
         when:
@@ -70,11 +70,11 @@ class DefaultCacheFactoryTest extends Specification {
     public void "creates directory backed cache instance"() {
         when:
         def factory = factoryFactory.create()
-        def cache = factory.open(tmpDir.dir, "<display>", CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Shared, null)
+        def cache = factory.open(tmpDir.testDirectory, "<display>", CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Shared, null)
 
         then:
         cache instanceof DefaultPersistentDirectoryCache
-        cache.baseDir == tmpDir.dir
+        cache.baseDir == tmpDir.testDirectory
         cache.toString().startsWith "<display>"
 
         when:
@@ -87,11 +87,11 @@ class DefaultCacheFactoryTest extends Specification {
     public void "creates DelegateOnDemandPersistentDirectoryCache cache instance for LockMode.NONE"() {
             when:
             def factory = factoryFactory.create()
-            def cache = factory.open(tmpDir.dir, "<display>", CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.None, null)
+            def cache = factory.open(tmpDir.testDirectory, "<display>", CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.None, null)
 
             then:
             cache instanceof DelegateOnDemandPersistentDirectoryCache
-            cache.baseDir == tmpDir.dir
+            cache.baseDir == tmpDir.testDirectory
             cache.toString().startsWith "On Demand Cache for <display>"
 
             when:
@@ -104,7 +104,7 @@ class DefaultCacheFactoryTest extends Specification {
     public void "creates indexed cache instance"() {
         when:
         def factory = factoryFactory.create()
-        def cache = factory.openIndexedCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, new DefaultSerializer())
+        def cache = factory.openIndexedCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, new DefaultSerializer())
 
         then:
         cache instanceof MultiProcessSafePersistentIndexedCache
@@ -113,7 +113,7 @@ class DefaultCacheFactoryTest extends Specification {
     public void "creates state cache instance"() {
         when:
         def factory = factoryFactory.create()
-        def cache = factory.openStateCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, new DefaultSerializer())
+        def cache = factory.openStateCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, new DefaultSerializer())
 
         then:
         cache instanceof SimpleStateCache
@@ -122,8 +122,8 @@ class DefaultCacheFactoryTest extends Specification {
     public void "reuses directory backed cache instances"() {
         when:
         def factory = factoryFactory.create()
-        def ref1 = factory.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        def ref2 = factory.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref1 = factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref2 = factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         then:
         ref1.is(ref2)
@@ -134,8 +134,8 @@ class DefaultCacheFactoryTest extends Specification {
         when:
         def factory1 = factoryFactory.create()
         def factory2 = factoryFactory.create()
-        def ref1 = factory1.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        def ref2 = factory2.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref1 = factory1.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref2 = factory2.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         then:
         ref1.is(ref2)
@@ -145,8 +145,8 @@ class DefaultCacheFactoryTest extends Specification {
     public void "reuses directory backed store instances"() {
         when:
         def factory = factoryFactory.create()
-        def ref1 = factory.openStore(tmpDir.dir, null, FileLockManager.LockMode.Exclusive, null)
-        def ref2 = factory.openStore(tmpDir.dir, null, FileLockManager.LockMode.Exclusive, null)
+        def ref1 = factory.openStore(tmpDir.testDirectory, null, FileLockManager.LockMode.Exclusive, null)
+        def ref2 = factory.openStore(tmpDir.testDirectory, null, FileLockManager.LockMode.Exclusive, null)
 
         then:
         ref1.is(ref2)
@@ -157,8 +157,8 @@ class DefaultCacheFactoryTest extends Specification {
         when:
         def factory1 = factoryFactory.create()
         def factory2 = factoryFactory.create()
-        def ref1 = factory1.openStore(tmpDir.dir, null, FileLockManager.LockMode.Exclusive, null)
-        def ref2 = factory2.openStore(tmpDir.dir, null, FileLockManager.LockMode.Exclusive, null)
+        def ref1 = factory1.openStore(tmpDir.testDirectory, null, FileLockManager.LockMode.Exclusive, null)
+        def ref2 = factory2.openStore(tmpDir.testDirectory, null, FileLockManager.LockMode.Exclusive, null)
 
         then:
         ref1.is(ref2)
@@ -168,8 +168,8 @@ class DefaultCacheFactoryTest extends Specification {
     public void "reuses indexed cache instances"() {
         when:
         def factory = factoryFactory.create()
-        def ref1 = factory.openIndexedCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        def ref2 = factory.openIndexedCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref1 = factory.openIndexedCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref2 = factory.openIndexedCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         then:
         ref1.is(ref2)
@@ -180,8 +180,8 @@ class DefaultCacheFactoryTest extends Specification {
         when:
         def factory1 = factoryFactory.create()
         def factory2 = factoryFactory.create()
-        def ref1 = factory1.openIndexedCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        def ref2 = factory2.openIndexedCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref1 = factory1.openIndexedCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref2 = factory2.openIndexedCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         then:
         ref1.is(ref2)
@@ -191,8 +191,8 @@ class DefaultCacheFactoryTest extends Specification {
     public void "reuses state cache instances"() {
         when:
         def factory = factoryFactory.create()
-        def ref1 = factory.openStateCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        def ref2 = factory.openStateCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref1 = factory.openStateCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref2 = factory.openStateCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         then:
         ref1.is(ref2)
@@ -203,8 +203,8 @@ class DefaultCacheFactoryTest extends Specification {
         when:
         def factory1 = factoryFactory.create()
         def factory2 = factoryFactory.create()
-        def ref1 = factory1.openStateCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        def ref2 = factory2.openStateCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref1 = factory1.openStateCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref2 = factory2.openStateCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         then:
         ref1.is(ref2)
@@ -215,8 +215,8 @@ class DefaultCacheFactoryTest extends Specification {
         given:
         def factory1 = factoryFactory.create()
         def factory2 = factoryFactory.create()
-        factory1.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        def oldCache = factory2.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory1.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def oldCache = factory2.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         when:
         factory1.close()
@@ -227,7 +227,7 @@ class DefaultCacheFactoryTest extends Specification {
 
         when:
         def factory = factoryFactory.create()
-        def cache = factory.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def cache = factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         then:
         !cache.is(oldCache)
@@ -239,8 +239,8 @@ class DefaultCacheFactoryTest extends Specification {
         given:
         def factory1 = factoryFactory.create()
         def factory2 = factoryFactory.create()
-        factory1.openIndexedCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        def oldCache = factory2.openIndexedCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory1.openIndexedCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def oldCache = factory2.openIndexedCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         when:
         factory1.close()
@@ -251,7 +251,7 @@ class DefaultCacheFactoryTest extends Specification {
 
         when:
         def factory = factoryFactory.create()
-        def cache = factory.openIndexedCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def cache = factory.openIndexedCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         then:
         !cache.is(oldCache)
@@ -263,8 +263,8 @@ class DefaultCacheFactoryTest extends Specification {
         given:
         def factory1 = factoryFactory.create()
         def factory2 = factoryFactory.create()
-        factory1.openStateCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        def oldCache = factory2.openStateCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory1.openStateCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def oldCache = factory2.openStateCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         when:
         factory1.close()
@@ -275,7 +275,7 @@ class DefaultCacheFactoryTest extends Specification {
 
         when:
         def factory = factoryFactory.create()
-        def cache = factory.openStateCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def cache = factory.openStateCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         then:
         !cache.is(oldCache)
@@ -287,10 +287,10 @@ class DefaultCacheFactoryTest extends Specification {
         given:
         def factory1 = factoryFactory.create()
         def factory2 = factoryFactory.create()
-        def oldCache = factory1.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        factory2.openIndexedCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        factory2.openStateCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
-        factory2.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def oldCache = factory1.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory2.openIndexedCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory2.openStateCache(tmpDir.testDirectory, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory2.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         when:
         factory1.close()
@@ -301,7 +301,7 @@ class DefaultCacheFactoryTest extends Specification {
 
         when:
         def factory = factoryFactory.create()
-        def cache = factory.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def cache = factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         then:
         !oldCache.is(cache)
@@ -312,64 +312,64 @@ class DefaultCacheFactoryTest extends Specification {
     public void "fails when directory cache is already open with different properties"() {
         given:
         def factory = factoryFactory.create()
-        factory.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         when:
-        factory.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'other'], FileLockManager.LockMode.Exclusive, null)
+        factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'other'], FileLockManager.LockMode.Exclusive, null)
 
         then:
         IllegalStateException e = thrown()
-        e.message == "Cache '${tmpDir.dir}' is already open with different state."
+        e.message == "Cache '${tmpDir.testDirectory}' is already open with different state."
     }
 
     public void "fails when directory cache is already open with different properties in different session"() {
         given:
         def factory1 = factoryFactory.create()
-        factory1.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory1.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         when:
         def factory2 = factoryFactory.create()
-        factory2.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'other'], FileLockManager.LockMode.Exclusive, null)
+        factory2.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'other'], FileLockManager.LockMode.Exclusive, null)
 
         then:
         IllegalStateException e = thrown()
-        e.message == "Cache '${tmpDir.dir}' is already open with different state."
+        e.message == "Cache '${tmpDir.testDirectory}' is already open with different state."
     }
 
     public void "fails when directory cache is already open when rebuild is requested"() {
         given:
         def factory = factoryFactory.create()
-        factory.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         when:
-        factory.open(tmpDir.dir, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory.open(tmpDir.testDirectory, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         then:
         IllegalStateException e = thrown()
-        e.message == "Cannot rebuild cache '${tmpDir.dir}' as it is already open."
+        e.message == "Cannot rebuild cache '${tmpDir.testDirectory}' as it is already open."
     }
 
     public void "fails when directory cache is already open in different session when rebuild is requested"() {
         given:
         def factory1 = factoryFactory.create()
-        factory1.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory1.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         when:
         def factory2 = factoryFactory.create()
-        factory2.open(tmpDir.dir, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory2.open(tmpDir.testDirectory, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         then:
         IllegalStateException e = thrown()
-        e.message == "Cannot rebuild cache '${tmpDir.dir}' as it is already open."
+        e.message == "Cannot rebuild cache '${tmpDir.testDirectory}' as it is already open."
     }
 
     public void "can open directory cache when rebuild is requested and cache was rebuilt in same session"() {
         given:
         def factory = factoryFactory.create()
-        factory.open(tmpDir.dir, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory.open(tmpDir.testDirectory, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         when:
-        factory.open(tmpDir.dir, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory.open(tmpDir.testDirectory, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         then:
         notThrown(RuntimeException)
@@ -378,12 +378,12 @@ class DefaultCacheFactoryTest extends Specification {
     public void "can open directory cache when rebuild is requested and has been closed"() {
         given:
         def factory1 = factoryFactory.create()
-        factory1.open(tmpDir.dir, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory1.open(tmpDir.testDirectory, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
         factory1.close()
 
         when:
         def factory2 = factoryFactory.create()
-        factory2.open(tmpDir.dir, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory2.open(tmpDir.testDirectory, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
 
         then:
         notThrown(RuntimeException)
@@ -392,14 +392,14 @@ class DefaultCacheFactoryTest extends Specification {
     public void "fails when directory cache when cache is already open with different lock mode"() {
         given:
         def factory = factoryFactory.create()
-        factory.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Shared, null)
+        factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Shared, null)
 
         when:
-        factory.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'other'], FileLockManager.LockMode.Exclusive, null)
+        factory.open(tmpDir.testDirectory, null, CacheUsage.ON, null, [prop: 'other'], FileLockManager.LockMode.Exclusive, null)
 
         then:
         IllegalStateException e = thrown()
-        e.message == "Cannot open cache '${tmpDir.dir}' with exclusive lock mode as it is already open with shared lock mode."
+        e.message == "Cannot open cache '${tmpDir.testDirectory}' with exclusive lock mode as it is already open with shared lock mode."
     }
 
     public void "can pass CacheValidator to Cache"() {
@@ -408,7 +408,7 @@ class DefaultCacheFactoryTest extends Specification {
         CacheValidator validator = Mock()
 
         when:
-        def cache = factory1.open(tmpDir.dir, null, CacheUsage.ON, validator, [prop: 'value'], FileLockManager.LockMode.Shared, null)
+        def cache = factory1.open(tmpDir.testDirectory, null, CacheUsage.ON, validator, [prop: 'value'], FileLockManager.LockMode.Shared, null)
 
         then:
         validator.isValid() >>> [false, true]
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheRepositoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheRepositoryTest.groovy
index e85eacf..80931a3 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheRepositoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheRepositoryTest.groovy
@@ -20,20 +20,20 @@ import org.gradle.api.Action
 import org.gradle.api.Project
 import org.gradle.api.invocation.Gradle
 import org.gradle.cache.CacheBuilder.VersionStrategy
-import org.gradle.cache.DefaultSerializer
+import org.gradle.cache.CacheValidator
 import org.gradle.cache.PersistentCache
 import org.gradle.cache.PersistentIndexedCache
 import org.gradle.cache.PersistentStateCache
+import org.gradle.messaging.serialize.DefaultSerializer
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.GradleVersion
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
 import org.junit.Rule
 import spock.lang.Specification
-import org.gradle.cache.CacheValidator
 
 class DefaultCacheRepositoryTest extends Specification {
     @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder()
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     private final TestFile homeDir = tmpDir.createDir("home")
     private final TestFile buildRootDir = tmpDir.createDir("build")
     private final TestFile sharedCacheDir = homeDir.file("caches")
@@ -46,7 +46,7 @@ class DefaultCacheRepositoryTest extends Specification {
 
     public void setup() {
         Project project = Mock()
-        _ * cache.baseDir >> tmpDir.dir
+        _ * cache.baseDir >> tmpDir.testDirectory
         _ * gradle.rootProject >> project
         _ * project.projectDir >> buildRootDir
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerTest.groovy
index 66bb064..f43bc06 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerTest.groovy
@@ -19,9 +19,9 @@ package org.gradle.cache.internal
 import org.apache.commons.lang.RandomStringUtils
 import org.gradle.cache.internal.FileLockManager.LockMode
 import org.gradle.internal.Factory
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.Requires
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
 import org.gradle.util.TestPrecondition
 import org.junit.Rule
 import spock.lang.Specification
@@ -34,7 +34,7 @@ import static org.gradle.cache.internal.FileLockManager.LockMode.Shared
  * @author: Szczepan Faber, created at: 8/30/11
  */
 class DefaultFileLockManagerTest extends Specification {
-    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     def metaDataProvider = Mock(ProcessMetaDataProvider)
     FileLockManager manager = new DefaultFileLockManager(metaDataProvider)
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheSpec.groovy
index 40a473d..d31e055 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheSpec.groovy
@@ -16,17 +16,19 @@
 
 package org.gradle.cache.internal
 
-import spock.lang.Specification
-import org.junit.Rule
-import org.gradle.util.TemporaryFolder
 import org.gradle.CacheUsage
-import org.gradle.cache.CacheValidator
 import org.gradle.api.Action
-import static org.gradle.cache.internal.DefaultFileLockManagerTestHelper.*
+import org.gradle.cache.CacheValidator
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+import static org.gradle.cache.internal.DefaultFileLockManagerTestHelper.createDefaultFileLockManager
+import static org.gradle.cache.internal.DefaultFileLockManagerTestHelper.unlockUncleanly
 
 class DefaultPersistentDirectoryCacheSpec extends Specification {
 
-    @Rule TemporaryFolder tmp
+    @Rule TestNameTestDirectoryProvider tmp
     
     def "will rebuild cache if not unlocked cleanly"() {
         given:
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheTest.java b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheTest.java
index ae5a442..fff1a16 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheTest.java
@@ -20,10 +20,10 @@ import org.gradle.api.Action;
 import org.gradle.cache.CacheOpenException;
 import org.gradle.cache.CacheValidator;
 import org.gradle.cache.PersistentCache;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.GUtil;
 import org.gradle.util.JUnit4GroovyMockery;
-import org.gradle.util.TemporaryFolder;
-import org.gradle.util.TestFile;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -39,14 +39,12 @@ import java.util.Properties;
 
 import static org.gradle.cache.internal.FileLockManager.LockMode;
 import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.*;
 
 @RunWith(JMock.class)
 public class DefaultPersistentDirectoryCacheTest {
     @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private final JUnit4Mockery context = new JUnit4GroovyMockery();
     private final ProcessMetaDataProvider metaDataProvider = context.mock(ProcessMetaDataProvider.class);
     private final FileLockManager lockManager = new DefaultFileLockManager(metaDataProvider);
@@ -67,7 +65,7 @@ public class DefaultPersistentDirectoryCacheTest {
 
     @Test
     public void initialisesCacheWhenCacheDirDoesNotExist() {
-        TestFile emptyDir = tmpDir.getDir().file("dir");
+        TestFile emptyDir = tmpDir.getTestDirectory().file("dir");
         emptyDir.assertDoesNotExist();
 
         context.checking(new Expectations() {{
@@ -81,7 +79,7 @@ public class DefaultPersistentDirectoryCacheTest {
 
     @Test
     public void initializesCacheWhenPropertiesFileDoesNotExist() {
-        TestFile dir = tmpDir.getDir().file("dir").createDir();
+        TestFile dir = tmpDir.getTestDirectory().file("dir").createDir();
 
         context.checking(new Expectations() {{
             one(action).execute(with(notNullValue(PersistentCache.class)));
@@ -162,7 +160,7 @@ public class DefaultPersistentDirectoryCacheTest {
 
     @Test
     public void rebuildsCacheWhenInitialiserFailedOnPreviousOpen() {
-        TestFile dir = tmpDir.getDir().file("dir").createDir();
+        TestFile dir = tmpDir.getTestDirectory().file("dir").createDir();
         final RuntimeException failure = new RuntimeException();
 
         context.checking(new Expectations() {{
@@ -206,7 +204,7 @@ public class DefaultPersistentDirectoryCacheTest {
     }
 
     private TestFile createCacheDir(String... extraProps) {
-        TestFile dir = tmpDir.getDir();
+        TestFile dir = tmpDir.getTestDirectory();
 
         Map<String, Object> properties = new HashMap<String, Object>();
         properties.putAll(this.properties);
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStoreTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStoreTest.groovy
index d7c5e33..d583841 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStoreTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStoreTest.groovy
@@ -15,15 +15,16 @@
  */
 package org.gradle.cache.internal
 
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
+
 import static org.gradle.cache.internal.FileLockManager.LockMode.None
 import static org.gradle.cache.internal.FileLockManager.LockMode.Shared
 
 class DefaultPersistentDirectoryStoreTest extends Specification {
     @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     final FileLockManager lockManager = Mock()
     final FileLock lock = Mock()
     final cacheDir = tmpDir.file("dir")
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCacheSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCacheSpec.groovy
index 8e34194..710c33e 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCacheSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCacheSpec.groovy
@@ -16,16 +16,16 @@
 
 package org.gradle.cache.internal
 
-import spock.lang.Specification
+import org.gradle.cache.CacheOpenException
+import org.gradle.internal.Factory
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
-import org.gradle.util.TemporaryFolder
+import spock.lang.Specification
 import spock.lang.Unroll
-import org.gradle.internal.Factory
-import org.gradle.cache.CacheOpenException
 
 class DelegateOnDemandPersistentDirectoryCacheSpec extends Specification {
 
-    @Rule public TemporaryFolder tmpDir = new TemporaryFolder();
+    @Rule public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     DefaultPersistentDirectoryCache delegateCache = Mock()
     File dir = tmpDir.createDir("cacheDir")
     String displayName = "test display name"
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/OnDemandFileAccessTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/OnDemandFileAccessTest.groovy
index 41c06d6..e5cd076 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/OnDemandFileAccessTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/OnDemandFileAccessTest.groovy
@@ -17,7 +17,7 @@ package org.gradle.cache.internal
 
 import org.gradle.cache.internal.FileLockManager.LockMode
 import org.gradle.internal.Factory
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -25,7 +25,7 @@ class OnDemandFileAccessTest extends Specification {
     final FileLockManager manager = Mock()
     final FileLock targetLock = Mock()
 
-    @Rule TemporaryFolder dir = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider dir = new TestNameTestDirectoryProvider()
     OnDemandFileAccess lock
     File file
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/SimpleStateCacheTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/SimpleStateCacheTest.groovy
index 0fc5896..a9d4097 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/SimpleStateCacheTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/SimpleStateCacheTest.groovy
@@ -15,17 +15,17 @@
  */
 package org.gradle.cache.internal
 
-import org.gradle.cache.DefaultSerializer
 import org.gradle.cache.PersistentStateCache
+import org.gradle.messaging.serialize.DefaultSerializer
 import org.gradle.messaging.serialize.Serializer
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
-import static org.gradle.cache.internal.DefaultFileLockManagerTestHelper.isIntegrityViolated
+
 import static org.gradle.cache.internal.DefaultFileLockManagerTestHelper.*
 
 class SimpleStateCacheTest extends Specification {
-    @Rule public TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final FileAccess fileAccess = Mock()
     final File file = tmpDir.file("state.bin")
     final SimpleStateCache<String> cache = createStateCache(fileAccess)
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCacheTest.java b/subprojects/core/src/test/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCacheTest.java
index ec4f539..272a629 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCacheTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCacheTest.java
@@ -15,10 +15,10 @@
  */
 package org.gradle.cache.internal.btree;
 
-import org.gradle.cache.DefaultSerializer;
+import org.gradle.messaging.serialize.DefaultSerializer;
 import org.gradle.messaging.serialize.Serializer;
-import org.gradle.util.TemporaryFolder;
-import org.gradle.util.TestFile;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -34,7 +34,7 @@ import static org.junit.Assert.assertThat;
 
 public class BTreePersistentIndexedCacheTest {
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private final Serializer<String> stringSerializer = new DefaultSerializer<String>();
     private final Serializer<Integer> integerSerializer = new DefaultSerializer<Integer>();
     private BTreePersistentIndexedCache<String, Integer> cache;
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultBuildConfigurerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultBuildConfigurerTest.groovy
index f3c8a65..a030173 100644
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultBuildConfigurerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultBuildConfigurerTest.groovy
@@ -15,26 +15,42 @@
  */
 package org.gradle.configuration
 
+import org.gradle.StartParameter
+import org.gradle.api.Action
 import org.gradle.api.internal.GradleInternal
 import org.gradle.api.internal.project.ProjectInternal
 import spock.lang.Specification
-import org.gradle.api.Action
 
 class DefaultBuildConfigurerTest extends Specification {
-    private final GradleInternal gradle = Mock()
-    private final ProjectInternal rootProject = Mock()
-    private final Action<? super ProjectInternal> action = Mock()
-    private final DefaultBuildConfigurer configurer = new DefaultBuildConfigurer(action)
+    private startParameter = Mock(StartParameter)
+    private gradle = Mock(GradleInternal)
+    private rootProject = Mock(ProjectInternal)
+    private action = Mock(Action)
+    private configurer = new DefaultBuildConfigurer(action)
 
     def executesActionsForEachProject() {
         when:
         configurer.configure(gradle)
 
         then:
+        1 * gradle.startParameter >> startParameter
         _ * gradle.rootProject >> rootProject
         1 * rootProject.allprojects(!null) >> { args ->
             args[0].execute(rootProject)
         }
         1 * action.execute(rootProject)
     }
+
+    def "works in configure on demand mode"() {
+        when:
+        configurer.configure(gradle)
+
+        then:
+        1 * startParameter.isConfigureOnDemand() >> true
+        1 * gradle.startParameter >> startParameter
+        1 * gradle.addProjectEvaluationListener(_ as ImplicitTasksConfigurer)
+        1 * gradle.rootProject >> rootProject
+        1 * rootProject.evaluate()
+        0 * _._
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/ImplicitTasksConfigurerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/ImplicitTasksConfigurerTest.groovy
index 47638c0..47f0b14 100644
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/ImplicitTasksConfigurerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/ImplicitTasksConfigurerTest.groovy
@@ -30,4 +30,12 @@ class ImplicitTasksConfigurerTest extends Specification {
         then:
         project.plugins.hasPlugin('help-tasks')
     }
+
+    def "applies help tasks after evaluate"() {
+        when:
+        configurer.afterEvaluate(project, null)
+
+        then:
+        project.plugins.hasPlugin('help-tasks')
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/OnlyWhenConfigureOnDemandTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/OnlyWhenConfigureOnDemandTest.groovy
new file mode 100644
index 0000000..39fb871
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/OnlyWhenConfigureOnDemandTest.groovy
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.execution
+
+import org.gradle.StartParameter
+import org.gradle.api.internal.GradleInternal
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 1/8/13
+ */
+class OnlyWhenConfigureOnDemandTest extends Specification {
+
+    private delegate = Mock(BuildConfigurationAction)
+    private onlyWhen = new OnlyWhenConfigureOnDemand(delegate)
+    private context = Mock(BuildExecutionContext)
+    private startParameter = Mock(StartParameter)
+    private gradle = Mock(GradleInternal)
+
+    def "does not do anything when configure on demand is off"() {
+        given:
+        startParameter.configureOnDemand >> false
+
+        when:
+        onlyWhen.configure(context)
+
+        then:
+        1 * context.gradle >> gradle
+        1 * gradle.startParameter >> startParameter
+        1 * startParameter.configureOnDemand >> false
+        1 * context.proceed()
+        0 * _._
+    }
+
+    def "delegates when configure on demand is off"() {
+        given:
+        startParameter.configureOnDemand >> false
+
+        when:
+        onlyWhen.configure(context)
+
+        then:
+        1 * context.gradle >> gradle
+        1 * gradle.startParameter >> startParameter
+        1 * startParameter.configureOnDemand >> true
+        1 * delegate.configure(context)
+        0 * _._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/ProjectEvaluatingActionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/ProjectEvaluatingActionTest.groovy
new file mode 100644
index 0000000..a68a457
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/ProjectEvaluatingActionTest.groovy
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.execution
+
+import org.gradle.StartParameter
+import org.gradle.api.internal.GradleInternal
+import org.gradle.api.internal.project.DefaultProject
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 1/8/13
+ */
+class ProjectEvaluatingActionTest extends Specification {
+
+    private evaluator = Mock(TaskPathProjectEvaluator)
+    private context = Mock(BuildExecutionContext)
+    private startParameter = Mock(StartParameter)
+    private gradle = Mock(GradleInternal)
+    private project = Mock(DefaultProject)
+
+    private action = new ProjectEvaluatingAction(evaluator)
+
+    def setup() {
+        context.gradle >> gradle
+        gradle.startParameter >> startParameter
+        gradle.defaultProject >> project
+    }
+
+    def "evaluates projects by task paths and proceeds"() {
+        when:
+        action.configure(context)
+
+        then:
+        startParameter.taskNames >> ['foo', "bar:baz"]
+
+        1 * context.proceed()
+        1 * evaluator.evaluateByPath(project, 'foo')
+        1 * evaluator.evaluateByPath(project, 'bar:baz')
+        0 * evaluator._
+    }
+
+    def "evaluates the default project even if the task names are empty"() {
+        when:
+        action.configure(context)
+
+        then:
+        startParameter.taskNames >> []
+
+        1 * context.proceed()
+        1 * project.evaluate()
+        0 * project._
+        0 * evaluator._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/ProjectFinderByTaskPathTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/ProjectFinderByTaskPathTest.groovy
new file mode 100644
index 0000000..493fbac
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/ProjectFinderByTaskPathTest.groovy
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.execution
+
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.testfixtures.ProjectBuilder
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 1/8/13
+ */
+class ProjectFinderByTaskPathTest extends Specification {
+
+    def finder = new ProjectFinderByTaskPath()
+
+    //root->foo->bar->baz
+    ProjectInternal root = new ProjectBuilder().withName("root").build()
+    ProjectInternal foo = new ProjectBuilder().withName("foo").withParent(root).build()
+    ProjectInternal bar = new ProjectBuilder().withName("bar").withParent(foo).build()
+
+    def "finds root"() {
+        expect:
+        finder.findProject(":", root) == root
+        finder.findProject(":", foo) == root
+        finder.findProject(":", bar) == root
+    }
+
+    def "finds project from root"() {
+        expect:
+        finder.findProject(":foo:bar:someTask", foo) == bar
+        finder.findProject(":foo:bar:someTask", bar) == bar
+        finder.findProject(":foo:bar:someTask", root) == bar
+        finder.findProject(":foo:someTask", bar) == foo
+        finder.findProject(":someTask", bar) == root
+    }
+
+    def "finds project relatively"() {
+        expect:
+        finder.findProject("foo:bar:someTask", root) == bar
+        finder.findProject("foo:someTask", root) == foo
+        finder.findProject("bar:someTask", foo) == bar
+    }
+
+    def "provides decent information when project cannot be found"() {
+        when:
+        finder.findProject("foo:xxx:someTask", root)
+        then:
+        def ex = thrown(ProjectFinderByTaskPath.ProjectLookupException)
+        ex.message.contains("xxx")
+    }
+
+    def "only allows valid task path as input"() {
+        when:
+        finder.findProject("someTaskName", foo)
+        then:
+        def ex = thrown(IllegalArgumentException)
+        ex.message.contains('someTaskName')
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationActionTest.java b/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationActionTest.java
index 74b210d..7314ea3 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationActionTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationActionTest.java
@@ -18,6 +18,7 @@ package org.gradle.execution;
 import com.google.common.collect.LinkedHashMultimap;
 import com.google.common.collect.Multimap;
 import org.gradle.StartParameter;
+import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.Task;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.project.AbstractProject;
@@ -372,7 +373,7 @@ public class TaskNameResolvingBuildConfigurationActionTest {
         try {
             action.configure(executionContext);
             fail();
-        } catch (TaskSelectionException e) {
+        } catch (InvalidUserDataException e) {
             assertThat(e.getMessage(), equalTo("Project 'a' is ambiguous in [project]. Candidates are: 'aa', 'ab'."));
         }
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/TaskPathProjectEvaluatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/TaskPathProjectEvaluatorTest.groovy
new file mode 100644
index 0000000..c56291d
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/TaskPathProjectEvaluatorTest.groovy
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.execution
+
+import org.gradle.api.internal.project.ProjectInternal
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 1/8/13
+ */
+class TaskPathProjectEvaluatorTest extends Specification {
+
+    private finder = Mock(ProjectFinderByTaskPath)
+    private project = Mock(ProjectInternal)
+
+    private evaluator = new TaskPathProjectEvaluator(finder)
+
+    def "evaluates task path"() {
+        def foundProject = Mock(ProjectInternal)
+
+        when:
+        evaluator.evaluateByPath(project, ":foo:bar")
+
+        then:
+        1 * finder.findProject(":foo:bar", project) >> foundProject
+        1 * foundProject.evaluate()
+        0 * _._
+    }
+
+    def "evaluates task name"() {
+        def subprojects = [Mock(ProjectInternal), Mock(ProjectInternal)]
+
+        when:
+        evaluator.evaluateByPath(project, "someTask")
+
+        then:
+        1 * project.evaluate()
+        1 * project.subprojects >> subprojects
+        1 * subprojects[0].evaluate()
+        1 * subprojects[1].evaluate()
+        0 * _._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/commandline/CommandLineTaskConfigurerSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/commandline/CommandLineTaskConfigurerSpec.groovy
index ee906c9..8742df0 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/commandline/CommandLineTaskConfigurerSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/commandline/CommandLineTaskConfigurerSpec.groovy
@@ -141,6 +141,16 @@ class CommandLineTaskConfigurerSpec extends Specification {
         ex.message.contains('xxx')
     }
 
+    def "fails neatly when short option used"() {
+        def args = ['--someFlag', '-c']
+        when:
+        configurer.configureTasks([task], args)
+
+        then:
+        def ex = thrown(GradleException)
+        ex.message.contains("Unknown command-line option '-c'")
+    }
+
     public static class SomeTask extends DefaultTask {
         String content = 'default content'
         @CommandLineOption(options="content", description="Some content.") public void setContent(String content) {
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/commandline/CommandLineTaskParserSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/commandline/CommandLineTaskParserSpec.groovy
index b7662da..269f3ad 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/commandline/CommandLineTaskParserSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/commandline/CommandLineTaskParserSpec.groovy
@@ -17,7 +17,6 @@
 package org.gradle.execution.commandline
 
 import org.gradle.api.DefaultTask
-import org.gradle.api.GradleException
 import org.gradle.api.Project
 import org.gradle.api.tasks.TaskAction
 import org.gradle.execution.TaskSelector
@@ -73,15 +72,6 @@ class CommandLineTaskParserSpec extends Specification {
         out.get('foo task') == [task, task2] as Set
     }
 
-    def "reports incorrect commandline task configuration"() {
-        when:
-        parser.parseTasks(['tasks', '-l', '-l'], selector)
-
-        then:
-        def ex = thrown(GradleException)
-        ex.message.contains('-l')
-    }
-
     def "parses multiple matching tasks"() {
         given:
         selector.getSelection('foo') >> new TaskSelector.TaskSelection('foo task', [task, task2] as Set)
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuterTest.java b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuterTest.java
index 7d9cd77..291bf8d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuterTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuterTest.java
@@ -16,6 +16,7 @@
 
 package org.gradle.execution.taskgraph;
 
+import groovy.lang.Closure;
 import org.gradle.api.CircularReferenceException;
 import org.gradle.api.Task;
 import org.gradle.api.execution.TaskExecutionGraphListener;
@@ -375,7 +376,8 @@ public class DefaultTaskGraphExecuterTest {
         final Task a = task("a");
         final Task b = task("b");
 
-        taskExecuter.beforeTask(toClosure(runnable));
+        final Closure closure = toClosure(runnable);
+        taskExecuter.beforeTask(closure);
 
         taskExecuter.addTasks(toList(a, b));
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/UriScriptSourceTest.java b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/UriScriptSourceTest.java
index e290eee..f5942d6 100644
--- a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/UriScriptSourceTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/UriScriptSourceTest.java
@@ -17,8 +17,8 @@
 package org.gradle.groovy.scripts;
 
 import org.gradle.api.internal.resource.UriResource;
-import org.gradle.util.TemporaryFolder;
-import org.gradle.util.TestFile;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -27,16 +27,16 @@ import java.io.File;
 import java.net.URI;
 import java.net.URISyntaxException;
 
-import static org.gradle.util.Matchers.*;
+import static org.gradle.util.Matchers.matchesRegexp;
 import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertThat;
 
 public class UriScriptSourceTest {
     private TestFile testDir;
     private File scriptFile;
     private URI scriptFileUri;
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
     @Before
     public void setUp() throws URISyntaxException {
@@ -47,7 +47,7 @@ public class UriScriptSourceTest {
     }
 
     private URI createJar() throws URISyntaxException {
-        TestFile jarFile = tmpDir.getDir().file("test.jar");
+        TestFile jarFile = tmpDir.getTestDirectory().file("test.jar");
         testDir.file("ignoreme").write("content");
         testDir.zipTo(jarFile);
         return new URI(String.format("jar:%s!/build.script", jarFile.toURI()));
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandlerTest.java b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandlerTest.java
index d6190be..d960ced 100644
--- a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandlerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandlerTest.java
@@ -32,7 +32,7 @@ import org.gradle.groovy.scripts.ScriptCompilationException;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.groovy.scripts.StringScriptSource;
 import org.gradle.groovy.scripts.Transformer;
-import org.gradle.util.TemporaryFolder;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -46,6 +46,7 @@ import java.io.File;
 import java.io.IOException;
 
 import static org.gradle.util.Matchers.containsLine;
+import static org.gradle.util.Matchers.isA;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
@@ -74,7 +75,7 @@ public class DefaultScriptCompilationHandlerTest {
 
     private JUnit4Mockery context = new JUnit4Mockery();
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
     @Before
     public void setUp() throws IOException, ClassNotFoundException {
@@ -152,7 +153,7 @@ public class DefaultScriptCompilationHandlerTest {
 
         Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
                 expectedScriptClass).newInstance();
-        assertThat(script, is(expectedScriptClass));
+        assertThat(script, isA(expectedScriptClass));
     }
 
     @Test
@@ -164,7 +165,7 @@ public class DefaultScriptCompilationHandlerTest {
 
         Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
                 expectedScriptClass).newInstance();
-        assertThat(script, is(expectedScriptClass));
+        assertThat(script, isA(expectedScriptClass));
     }
 
     @Test
@@ -176,7 +177,7 @@ public class DefaultScriptCompilationHandlerTest {
 
         Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
                 expectedScriptClass).newInstance();
-        assertThat(script, is(expectedScriptClass));
+        assertThat(script, isA(expectedScriptClass));
     }
 
     @Test
@@ -188,7 +189,7 @@ public class DefaultScriptCompilationHandlerTest {
 
         Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
                 expectedScriptClass).newInstance();
-        assertThat(script, is(expectedScriptClass));
+        assertThat(script, isA(expectedScriptClass));
     }
 
     @Test
@@ -200,7 +201,7 @@ public class DefaultScriptCompilationHandlerTest {
 
         Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
                 expectedScriptClass).newInstance();
-        assertThat(script, is(expectedScriptClass));
+        assertThat(script, isA(expectedScriptClass));
     }
 
     @Test
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildFileProjectSpecTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/BuildFileProjectSpecTest.java
index 00b495d..69ce214 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildFileProjectSpecTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/BuildFileProjectSpecTest.java
@@ -18,24 +18,25 @@ package org.gradle.initialization;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.internal.project.IProjectRegistry;
 import org.gradle.api.internal.project.ProjectIdentifier;
-import org.gradle.util.TemporaryFolder;
-import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.*;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.*;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.File;
 
+import static org.gradle.util.WrapUtil.toSet;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
 @RunWith(JMock.class)
 public class BuildFileProjectSpecTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private final File file = tmpDir.createFile("build");
     private final BuildFileProjectSpec spec = new BuildFileProjectSpec(file);
     private int counter;
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
index 422effd..6efe873 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
@@ -28,9 +28,9 @@ import org.gradle.cache.CacheRepository
 import org.gradle.cache.DirectoryCacheBuilder
 import org.gradle.cache.PersistentCache
 import org.gradle.cache.internal.FileLockManager
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
 import org.jmock.api.Action
 import org.junit.After
 import org.junit.Before
@@ -46,7 +46,7 @@ import static org.junit.Assert.assertEquals
  */
 @RunWith(org.jmock.integration.junit4.JMock)
 class BuildSourceBuilderTest {
-    @Rule public TemporaryFolder tmpDir = new TemporaryFolder();
+    @Rule public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     JUnit4GroovyMockery context = new JUnit4GroovyMockery()
     BuildSourceBuilder buildSourceBuilder
     GradleLauncherFactory gradleFactoryMock = context.mock(GradleLauncherFactory.class)
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/CompositeInitScriptFinderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/CompositeInitScriptFinderTest.groovy
index 3765d00..7c881df 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/CompositeInitScriptFinderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/CompositeInitScriptFinderTest.groovy
@@ -16,22 +16,20 @@
 package org.gradle.initialization
 
 import spock.lang.Specification
-import org.gradle.api.internal.GradleInternal
 
 class CompositeInitScriptFinderTest extends Specification {
     final InitScriptFinder target1 = Mock()
     final InitScriptFinder target2 = Mock()
-    final GradleInternal gradle = Mock()
     final CompositeInitScriptFinder finder = new CompositeInitScriptFinder(target1, target2)
     
     def "collects up scripts from all finders"() {
         def result = []
 
         when:
-        finder.findScripts(gradle, result)
+        finder.findScripts(result)
 
         then:
-        1 * target1.findScripts(gradle, result)
-        1 * target2.findScripts(gradle, result)
+        1 * target1.findScripts(result)
+        1 * target2.findScripts(result)
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java
index 049c178..63315d9 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java
@@ -22,8 +22,8 @@ import org.gradle.StartParameter;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.cli.CommandLineArgumentException;
 import org.gradle.logging.ShowStacktrace;
-import org.gradle.util.TemporaryFolder;
-import org.gradle.util.TestFile;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -42,7 +42,7 @@ import static org.junit.Assert.assertThat;
  */
 public class DefaultCommandLineConverterTest {
     @Rule
-    public TemporaryFolder testDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider testDir = new TestNameTestDirectoryProvider();
 
     private TestFile currentDir = testDir.file("current-dir");
     private File expectedBuildFile;
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherTest.java
index eb16a81..3e42d2b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherTest.java
@@ -29,9 +29,9 @@ import org.gradle.configuration.BuildConfigurer;
 import org.gradle.execution.BuildExecuter;
 import org.gradle.execution.TaskGraphExecuter;
 import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.HelperUtil;
 import org.gradle.util.JUnit4GroovyMockery;
-import org.gradle.util.TemporaryFolder;
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
@@ -78,7 +78,7 @@ public class DefaultGradleLauncherTest {
     private TasksCompletionListener tasksCompletionListener = context.mock(TasksCompletionListener.class);
 
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
     @Before
     public void setUp() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradlePropertiesLoaderTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradlePropertiesLoaderTest.java
index 0d63bd8..bc19764 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradlePropertiesLoaderTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradlePropertiesLoaderTest.java
@@ -17,10 +17,9 @@ package org.gradle.initialization;
 
 import org.gradle.StartParameter;
 import org.gradle.api.Project;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.GUtil;
-import org.gradle.util.TemporaryFolder;
 import org.gradle.util.WrapUtil;
-import static org.junit.Assert.*;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -31,6 +30,8 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
 
+import static org.junit.Assert.*;
+
 /**
  * @author Hans Dockter
  */
@@ -42,7 +43,7 @@ public class DefaultGradlePropertiesLoaderTest {
     private Map<String, String> envProperties = new HashMap<String, String>();
     private StartParameter startParameter = new StartParameter();
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
     @Before
     public void setUp() {
@@ -205,7 +206,7 @@ public class DefaultGradlePropertiesLoaderTest {
 
     @Test
     public void loadPropertiesWithNoExceptionForNonExistingUserHomeAndSettingsDir() {
-        tmpDir.getDir().deleteDir();
+        tmpDir.getTestDirectory().deleteDir();
         gradlePropertiesLoader.loadProperties(settingsDir, startParameter, systemProperties, envProperties);
     }
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DistributionInitScriptFinderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/DistributionInitScriptFinderTest.groovy
index 4c34165..ac4bb65 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DistributionInitScriptFinderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DistributionInitScriptFinderTest.groovy
@@ -15,19 +15,14 @@
  */
 package org.gradle.initialization
 
-import org.gradle.api.internal.GradleDistributionLocator
-import org.gradle.api.internal.GradleInternal
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
-import org.gradle.groovy.scripts.UriScriptSource
 
 class DistributionInitScriptFinderTest extends Specification {
-    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final def distDir = tmpDir.createDir("gradle-home")
-    final GradleDistributionLocator locator = Mock()
-    final GradleInternal gradle = Mock()
-    final DistributionInitScriptFinder finder = new DistributionInitScriptFinder(locator)
+    final DistributionInitScriptFinder finder = new DistributionInitScriptFinder(distDir)
 
     def setup() {
     }
@@ -35,11 +30,8 @@ class DistributionInitScriptFinderTest extends Specification {
     def "does nothing when init.d directory does not exist there is no distribution"() {
         def scripts = []
 
-        given:
-        _ * locator.gradleHome >> null
-
         when:
-        finder.findScripts(gradle, scripts)
+        finder.findScripts(scripts)
 
         then:
         scripts.empty
@@ -48,11 +40,8 @@ class DistributionInitScriptFinderTest extends Specification {
     def "does nothing when init.d directory does not exist in distribution"() {
         def scripts = []
 
-        given:
-        _ * locator.gradleHome >> distDir
-
         when:
-        finder.findScripts(gradle, scripts)
+        finder.findScripts(scripts)
 
         then:
         scripts.empty
@@ -67,19 +56,12 @@ class DistributionInitScriptFinderTest extends Specification {
         distDir.createFile("init.d/readme.txt")
         distDir.createFile("init.d/lib/test.jar")
 
-        and:
-        _ * locator.gradleHome >> distDir
-
         when:
-        finder.findScripts(gradle, scripts)
+        finder.findScripts(scripts)
 
         then:
         scripts.size() == 2
-        scripts.find {
-            it instanceof UriScriptSource && it.resource.sourceFile == script1
-        }
-        scripts.find {
-            it instanceof UriScriptSource && it.resource.sourceFile == script2
-        }
+        script1 in scripts
+        script2 in scripts
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/InitScriptHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/InitScriptHandlerTest.groovy
index a90067d..132cdb9 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/InitScriptHandlerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/InitScriptHandlerTest.groovy
@@ -15,31 +15,40 @@
  */
 package org.gradle.initialization
 
-import spock.lang.Specification
-import org.gradle.configuration.InitScriptProcessor
+import org.gradle.StartParameter
 import org.gradle.api.internal.GradleInternal
-import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.configuration.InitScriptProcessor
+import org.gradle.groovy.scripts.UriScriptSource
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
 
 class InitScriptHandlerTest extends Specification {
-    final InitScriptFinder finder = Mock()
+
+    @Rule TestNameTestDirectoryProvider testDirectoryProvider
+
     final InitScriptProcessor processor = Mock()
     final GradleInternal gradle = Mock()
-    final InitScriptHandler handler = new InitScriptHandler(finder, processor)
+    final InitScriptHandler handler = new InitScriptHandler(processor)
     
     def "finds and processes init scripts"() {
-        ScriptSource script1 = Mock()
-        ScriptSource script2 = Mock()
+        File script1 = testDirectoryProvider.createFile("script1.gradle")
+        File script2 = testDirectoryProvider.createFile("script2.gradle")
 
         when:
         handler.executeScripts(gradle)
         
         then:
-        1 * finder.findScripts(gradle, !null) >> {gradle, scripts -> 
-            scripts << script1
-            scripts << script2
+        _ * gradle.startParameter >> {
+            Mock(StartParameter) {
+                getAllInitScripts() >> {
+                    [script1, script2]
+                }
+            }
         }
-        1 * processor.process(script1, gradle)
-        1 * processor.process(script2, gradle)
+
+        1 * processor.process({ UriScriptSource source -> source.resource.file == script1 }, gradle)
+        1 * processor.process({ UriScriptSource source -> source.resource.file == script2 }, gradle)
         0 * _._
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/InstantiatingBuildLoaderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/InstantiatingBuildLoaderTest.groovy
index 63f5322..7a20c8f 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/InstantiatingBuildLoaderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/InstantiatingBuildLoaderTest.groovy
@@ -22,14 +22,15 @@ import org.gradle.api.internal.GradleInternal
 import org.gradle.api.internal.project.DefaultProject
 import org.gradle.api.internal.project.IProjectFactory
 import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.HelperUtil
 import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.TemporaryFolder
 import org.jmock.integration.junit4.JMock
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertThat
 
@@ -52,12 +53,12 @@ class InstantiatingBuildLoaderTest {
     ProjectInternal childProject
     GradleInternal build
     JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    @Rule public TemporaryFolder tmpDir = new TemporaryFolder();
+    @Rule public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
     @Before public void setUp()  {
         projectFactory = context.mock(IProjectFactory)
         buildLoader = new InstantiatingBuildLoader(projectFactory)
-        testDir = tmpDir.dir
+        testDir = tmpDir.testDirectory
         (rootProjectDir = new File(testDir, 'root')).mkdirs()
         (childProjectDir = new File(rootProjectDir, 'child')).mkdirs()
         startParameter.currentDir = rootProjectDir
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectDirectoryProjectSpecTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectDirectoryProjectSpecTest.java
index 7070c4f..d2aae08 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectDirectoryProjectSpecTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectDirectoryProjectSpecTest.java
@@ -18,25 +18,26 @@ package org.gradle.initialization;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.internal.project.IProjectRegistry;
 import org.gradle.api.internal.project.ProjectIdentifier;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.GFileUtils;
-import org.gradle.util.TemporaryFolder;
-import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.*;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.*;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.File;
 
+import static org.gradle.util.WrapUtil.toSet;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
 @RunWith(JMock.class)
 public class ProjectDirectoryProjectSpecTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private final File dir = tmpDir.createDir("build");
     private final ProjectDirectoryProjectSpec spec = new ProjectDirectoryProjectSpec(dir);
     private int counter;
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectPropertySettingBuildLoaderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectPropertySettingBuildLoaderTest.groovy
index 45cc9c7..76a3594 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectPropertySettingBuildLoaderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectPropertySettingBuildLoaderTest.groovy
@@ -21,13 +21,13 @@ import org.gradle.api.internal.GradleInternal
 import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.api.plugins.ExtensionContainer
 import org.gradle.api.plugins.ExtraPropertiesExtension
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.GUtil
-import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
 
 class ProjectPropertySettingBuildLoaderTest extends Specification {
-    @Rule public TemporaryFolder tmpDir = new TemporaryFolder();
+    @Rule public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     final BuildLoader target = Mock()
     final ProjectDescriptor projectDescriptor = Mock()
     final GradleInternal gradle = Mock()
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/ProvidedInitScriptFinderTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/ProvidedInitScriptFinderTest.java
deleted file mode 100644
index 7ae8f40..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/ProvidedInitScriptFinderTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.initialization;
-
-import org.gradle.util.GFileUtils;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.Expectations;
-import org.gradle.StartParameter;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.groovy.scripts.ScriptSource;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-import static org.hamcrest.Matchers.*;
-
-import java.util.List;
-import java.util.ArrayList;
-import java.io.File;
-
- at RunWith(JMock.class)
-public class ProvidedInitScriptFinderTest {
-    final JUnit4Mockery context = new JUnit4Mockery();
-    final ProvidedInitScriptFinder finder = new ProvidedInitScriptFinder();
-
-    @Test
-    public void testFindScripts() {
-
-        final GradleInternal gradleMock = context.mock(GradleInternal.class);
-        final StartParameter testStartParameter = new StartParameter();
-        testStartParameter.addInitScript(new File("some init script"));
-        testStartParameter.addInitScript(new File("/path/to/another init script"));
-
-        context.checking(new Expectations() {{
-            allowing(gradleMock).getStartParameter();
-            will(returnValue(testStartParameter));
-        }});
-
-        List<ScriptSource> sourceList = new ArrayList<ScriptSource>();
-        finder.findScripts(gradleMock, sourceList);
-        assertThat(getSourceFiles(sourceList), equalTo(canonicalise(testStartParameter.getInitScripts())));
-    }
-
-    private List<File> canonicalise(List<File> files) {
-        List<File> results = new ArrayList<File>();
-        for (File file : files) {
-            results.add(GFileUtils.canonicalise(file));
-        }
-        return results;
-    }
-
-    private List<File> getSourceFiles(List<ScriptSource> sources) {
-        List<File> results = new ArrayList<File>(sources.size());
-        for (ScriptSource source : sources) {
-            results.add(source.getResource().getFile());
-        }
-        return results;
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/UserHomeInitScriptFinderTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/UserHomeInitScriptFinderTest.java
index 78c9cb0..a1f0832 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/UserHomeInitScriptFinderTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/UserHomeInitScriptFinderTest.java
@@ -15,12 +15,7 @@
  */
 package org.gradle.initialization;
 
-import org.gradle.StartParameter;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.groovy.scripts.UriScriptSource;
-import org.gradle.util.TemporaryFolder;
-import org.jmock.Expectations;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.Before;
@@ -33,42 +28,34 @@ import java.util.ArrayList;
 import java.util.List;
 
 import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.instanceOf;
 import static org.junit.Assert.assertThat;
 
 @RunWith(JMock.class)
 public class UserHomeInitScriptFinderTest {
     @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private final JUnit4Mockery context = new JUnit4Mockery();
-    private final GradleInternal gradleMock = context.mock(GradleInternal.class);
-    private final StartParameter testStartParameter = new StartParameter();
-    private final UserHomeInitScriptFinder finder = new UserHomeInitScriptFinder();
+    private UserHomeInitScriptFinder finder;
 
     @Before
-    public void setup() {
-        testStartParameter.setGradleUserHomeDir(tmpDir.getDir());
-        context.checking(new Expectations() {{
-            allowing(gradleMock).getStartParameter();
-            will(returnValue(testStartParameter));
-        }});
+    public void setUp() throws Exception {
+        finder = new UserHomeInitScriptFinder(tmpDir.getTestDirectory());
     }
 
     @Test
     public void addsUserInitScriptWhenItExists() {
         File initScript = tmpDir.createFile("init.gradle");
 
-        List<ScriptSource> sourceList = new ArrayList<ScriptSource>();
-        finder.findScripts(gradleMock, sourceList);
+        List<File> sourceList = new ArrayList<File>();
+        finder.findScripts(sourceList);
         assertThat(sourceList.size(), equalTo(1));
-        assertThat(sourceList.get(0), instanceOf(UriScriptSource.class));
-        assertThat(sourceList.get(0).getResource().getFile(), equalTo(initScript));
+        assertThat(sourceList.get(0), equalTo(initScript));
     }
 
     @Test
     public void doesNotAddUserInitScriptsWhenTheyDoNotExist() {
-        List<ScriptSource> sourceList = new ArrayList<ScriptSource>();
-        finder.findScripts(gradleMock, sourceList);
+        List<File> sourceList = new ArrayList<File>();
+        finder.findScripts(sourceList);
         assertThat(sourceList.size(), equalTo(0));
     }
 
@@ -76,10 +63,9 @@ public class UserHomeInitScriptFinderTest {
     public void addsInitScriptsFromInitDirectoryWhenItExists() {
         File initScript = tmpDir.createFile("init.d/script.gradle");
 
-        List<ScriptSource> sourceList = new ArrayList<ScriptSource>();
-        finder.findScripts(gradleMock, sourceList);
+        List<File> sourceList = new ArrayList<File>();
+        finder.findScripts(sourceList);
         assertThat(sourceList.size(), equalTo(1));
-        assertThat(sourceList.get(0), instanceOf(UriScriptSource.class));
-        assertThat(sourceList.get(0).getResource().getFile(), equalTo(initScript));
+        assertThat(sourceList.get(0), equalTo(initScript));
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/layout/BuildLayoutFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/layout/BuildLayoutFactoryTest.groovy
index 53c2adc..c9c8775 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/layout/BuildLayoutFactoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/layout/BuildLayoutFactoryTest.groovy
@@ -15,20 +15,20 @@
  */
 package org.gradle.initialization.layout
 
-import org.gradle.util.TemporaryFolder
+import org.gradle.StartParameter
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.groovy.scripts.StringScriptSource
+import org.gradle.groovy.scripts.UriScriptSource
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
-import org.gradle.groovy.scripts.UriScriptSource
-import org.gradle.groovy.scripts.StringScriptSource
-import org.gradle.groovy.scripts.ScriptSource
-import org.gradle.StartParameter
 
 class BuildLayoutFactoryTest extends Specification {
-    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final BuildLayoutFactory locator = new BuildLayoutFactory()
 
     def "returns current directory when it contains a settings file"() {
-        def currentDir = tmpDir.dir
+        def currentDir = tmpDir.testDirectory
         def settingsFile = currentDir.createFile("settings.gradle")
 
         expect:
@@ -70,8 +70,8 @@ class BuildLayoutFactoryTest extends Specification {
 
         expect:
         def layout = locator.getLayoutFor(currentDir, true)
-        layout.rootDirectory == tmpDir.dir
-        layout.settingsDir == tmpDir.dir
+        layout.rootDirectory == tmpDir.testDirectory
+        layout.settingsDir == tmpDir.testDirectory
         refersTo(layout.settingsScriptSource, settingsFile)
     }
 
@@ -130,7 +130,7 @@ class BuildLayoutFactoryTest extends Specification {
         def currentDir = tmpDir.createDir("sub/current")
 
         expect:
-        def layout = locator.getLayoutFor(currentDir, tmpDir.dir)
+        def layout = locator.getLayoutFor(currentDir, tmpDir.testDirectory)
         layout.rootDirectory == currentDir
         layout.settingsDir == currentDir
         isEmpty(layout.settingsScriptSource)
diff --git a/subprojects/core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java b/subprojects/core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java
index 58342bf..f9c084e 100644
--- a/subprojects/core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java
@@ -31,6 +31,7 @@ import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.project.ServiceRegistryFactory;
 import org.gradle.api.invocation.Gradle;
 import org.gradle.execution.TaskGraphExecuter;
+import org.gradle.listener.ClosureBackedMethodInvocationDispatch;
 import org.gradle.listener.ListenerBroadcast;
 import org.gradle.listener.ListenerManager;
 import org.gradle.util.GradleVersion;
@@ -146,7 +147,7 @@ public class DefaultGradleTest {
     public void broadcastsBeforeProjectEvaluateEventsToClosures() {
         final Closure closure = HelperUtil.TEST_CLOSURE;
         context.checking(new Expectations() {{
-            one(projectEvaluationListenerBroadcast).add("beforeEvaluate", closure);
+            one(projectEvaluationListenerBroadcast).add(new ClosureBackedMethodInvocationDispatch("beforeEvaluate", closure));
         }});
 
         gradle.beforeProject(closure);
@@ -156,7 +157,7 @@ public class DefaultGradleTest {
     public void broadcastsAfterProjectEvaluateEventsToClosures() {
         final Closure closure = HelperUtil.TEST_CLOSURE;
         context.checking(new Expectations() {{
-            one(projectEvaluationListenerBroadcast).add("afterEvaluate", closure);
+            one(projectEvaluationListenerBroadcast).add(new ClosureBackedMethodInvocationDispatch("afterEvaluate", closure));
         }});
 
         gradle.afterProject(closure);
diff --git a/subprojects/core/src/test/groovy/org/gradle/listener/ActionBroadcastTest.groovy b/subprojects/core/src/test/groovy/org/gradle/listener/ActionBroadcastTest.groovy
index 3242a1d..1e4f2eb 100644
--- a/subprojects/core/src/test/groovy/org/gradle/listener/ActionBroadcastTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/listener/ActionBroadcastTest.groovy
@@ -15,8 +15,8 @@
  */
 package org.gradle.listener
 
-import spock.lang.Specification
 import org.gradle.api.Action
+import spock.lang.Specification
 
 class ActionBroadcastTest extends Specification {
     final ActionBroadcast<String> broadcast = new ActionBroadcast<String>()
@@ -33,16 +33,4 @@ class ActionBroadcastTest extends Specification {
         0 * action._
     }
 
-    def broadcastsEventsToClosure() {
-        Closure action = Mock()
-        broadcast.add(action)
-
-        when:
-        broadcast.execute('value')
-
-        then:
-        _ * action.maximumNumberOfParameters >> 1
-        1 * action.call('value')
-        0 * action._
-    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/listener/ListenerBroadcastTest.java b/subprojects/core/src/test/groovy/org/gradle/listener/ListenerBroadcastTest.java
index 8e95111..cee26e3 100644
--- a/subprojects/core/src/test/groovy/org/gradle/listener/ListenerBroadcastTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/listener/ListenerBroadcastTest.java
@@ -17,6 +17,7 @@
 package org.gradle.listener;
 
 import org.gradle.api.Action;
+import org.gradle.api.internal.ClosureBackedAction;
 import org.gradle.messaging.dispatch.Dispatch;
 import org.gradle.messaging.dispatch.MethodInvocation;
 import org.gradle.util.JUnit4GroovyMockery;
@@ -29,8 +30,8 @@ import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static org.gradle.util.HelperUtil.*;
-import static org.gradle.util.Matchers.*;
+import static org.gradle.util.HelperUtil.toClosure;
+import static org.gradle.util.Matchers.strictlyEqual;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
@@ -136,7 +137,7 @@ public class ListenerBroadcastTest {
             will(returnValue("ignore me"));
         }});
 
-        broadcast.add("event1", toClosure(testClosure));
+        broadcast.add("event1", new ClosureBackedAction<Object>(toClosure(testClosure)));
         broadcast.getSource().event1("param");
     }
 
@@ -144,13 +145,13 @@ public class ListenerBroadcastTest {
     public void doesNotNotifyClosureForOtherEventMethods() {
         final TestClosure testClosure = context.mock(TestClosure.class);
 
-        broadcast.add("event1", toClosure(testClosure));
+        broadcast.add("event1", new ClosureBackedAction<Object>(toClosure(testClosure)));
         broadcast.getSource().event2(9, "param");
     }
 
     @Test
     public void closureCanHaveFewerParametersThanEventMethod() {
-        broadcast.add("event2", toClosure("{ a -> 'result' }"));
+        broadcast.add("event2", new ClosureBackedAction<Object>(toClosure("{ a -> 'result' }")));
         broadcast.getSource().event2(1, "param");
         broadcast.getSource().event2(2, null);
     }
@@ -244,7 +245,7 @@ public class ListenerBroadcastTest {
             will(throwException(failure));
         }});
 
-        broadcast.add("event1", toClosure(testClosure));
+        broadcast.add("event1", new ClosureBackedAction<Object>(toClosure(testClosure)));
 
         try {
             broadcast.getSource().event1("param");
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleSpec.groovy
index d40fb1b..8707311 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleSpec.groovy
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-package org.gradle.process.internal;
-
+package org.gradle.process.internal
 
 import org.gradle.process.ExecResult
 import org.gradle.process.internal.streams.StreamsHandler
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.GUtil
 import org.gradle.util.Jvm
-import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Ignore
 import spock.lang.Specification
@@ -34,7 +33,7 @@ import java.util.concurrent.Callable
  */
 @Timeout(60)
 class DefaultExecHandleSpec extends Specification {
-    @Rule final TemporaryFolder tmpDir = new TemporaryFolder();
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
     void "forks process"() {
         given:
@@ -305,7 +304,7 @@ class DefaultExecHandleSpec extends Specification {
         new ExecHandleBuilder()
                 .executable(Jvm.current().getJavaExecutable().getAbsolutePath())
                 .setTimeout(20000) //sanity timeout
-                .workingDir(tmpDir.getDir());
+                .workingDir(tmpDir.getTestDirectory());
     }
 
     private List args(Class mainClass, String ... args) {
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessFactoryTest.java
deleted file mode 100644
index b88f61a..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessFactoryTest.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.process.internal;
-
-import org.gradle.api.Action;
-import org.gradle.api.internal.ClassPathRegistry;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.file.collections.SimpleFileCollection;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.internal.Factory;
-import org.gradle.internal.id.IdGenerator;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.MessagingServer;
-import org.gradle.messaging.remote.internal.inet.SocketInetAddress;
-import org.gradle.process.internal.child.IsolatedApplicationClassLoaderWorker;
-import org.gradle.process.internal.launcher.GradleWorkerMain;
-import org.hamcrest.Matchers;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.ObjectInputStream;
-import java.io.Serializable;
-import java.net.InetAddress;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Set;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertThat;
-
- at RunWith(JMock.class)
- at Ignore
-public class DefaultWorkerProcessFactoryTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final MessagingServer messagingServer = context.mock(MessagingServer.class);
-    private final ClassPathRegistry classPathRegistry = context.mock(ClassPathRegistry.class);
-    private final FileResolver fileResolver = context.mock(FileResolver.class);
-    private final IdGenerator<Object> idGenerator = context.mock(IdGenerator.class);
-    private final DefaultWorkerProcessFactory factory = new DefaultWorkerProcessFactory(LogLevel.LIFECYCLE, messagingServer, classPathRegistry, fileResolver,
-            idGenerator);
-
-    @Test
-    public void createsAndConfiguresAWorkerProcess() throws Exception {
-        final Set<File> processClassPath = Collections.singleton(new File("something.jar"));
-        final org.gradle.internal.classpath.ClassPath classPath = context.mock(org.gradle.internal.classpath.ClassPath.class);
-
-        context.checking(new Expectations() {{
-            one(classPathRegistry).getClassPath("WORKER_PROCESS");
-            will(returnValue(classPath));
-            allowing(classPath).getAsFiles();
-            will(returnValue(processClassPath));
-            allowing(fileResolver).resolveLater(".");
-            will(returnValue(new Factory<File>() {
-                public File create() {
-                    return new File(".");
-                }
-            }));
-            allowing(fileResolver).resolveFiles(with(Matchers.<Object>notNullValue()));
-            will(returnValue(new SimpleFileCollection()));
-        }});
-
-        WorkerProcessBuilder builder = factory.create();
-
-        assertThat(builder.getJavaCommand().getMain(), equalTo(GradleWorkerMain.class.getName()));
-        assertThat(builder.getLogLevel(), equalTo(LogLevel.LIFECYCLE));
-
-        builder.worker(new TestAction());
-        builder.applicationClasspath(Arrays.asList(new File("app.jar")));
-        builder.sharedPackages("package1", "package2");
-
-        final Address serverAddress = new SocketInetAddress(InetAddress.getByName("127.0.0.1"), 40);
-
-        context.checking(new Expectations(){{
-            one(messagingServer).accept(with(notNullValue(Action.class)));
-            will(returnValue(serverAddress));
-            one(idGenerator).generateId();
-            will(returnValue("<id>"));
-        }});
-
-        WorkerProcess process = builder.build();
-
-        assertThat(process, instanceOf(DefaultWorkerProcess.class));
-
-        ObjectInputStream instr = new ObjectInputStream(builder.getJavaCommand().getStandardInput());
-        assertThat(instr.readObject(), instanceOf(IsolatedApplicationClassLoaderWorker.class));
-    }
-
-    private static class TestAction implements Action<WorkerProcessContext>, Serializable {
-        public void execute(WorkerProcessContext workerProcessContext) {
-            throw new UnsupportedOperationException();
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessTest.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessTest.groovy
index 1a3d771..2265f3c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessTest.groovy
@@ -18,18 +18,21 @@
 
 package org.gradle.process.internal
 
-import org.junit.runner.RunWith
-import org.jmock.integration.junit4.JMock
+import org.gradle.messaging.remote.ConnectionAcceptor
+import org.gradle.messaging.remote.ObjectConnection
+import org.gradle.process.ExecResult
+import org.gradle.util.JUnit4GroovyMockery
 import org.gradle.util.MultithreadedTestCase
 import org.jmock.Mockery
-import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.messaging.remote.ConnectEvent
-import org.gradle.messaging.remote.ObjectConnection
+import org.jmock.integration.junit4.JMock
 import org.junit.Test
-import static org.hamcrest.Matchers.*
+import org.junit.runner.RunWith
+
 import java.util.concurrent.TimeUnit
-import static org.junit.Assert.*
-import org.gradle.process.ExecResult
+
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.assertThat
+import static org.junit.Assert.fail
 
 @RunWith(JMock.class)
 class DefaultWorkerProcessTest extends MultithreadedTestCase {
@@ -41,16 +44,19 @@ class DefaultWorkerProcessTest extends MultithreadedTestCase {
     @Test
     public void startsChildProcessAndBlocksUntilConnectionEstablished() {
         expectAttachesListener()
+        ConnectionAcceptor acceptor = context.mock(ConnectionAcceptor.class)
+        workerProcess.startAccepting(acceptor)
 
         context.checking {
             one(execHandle).start()
             will {
                 start {
                     expectUnblocks {
-                        workerProcess.getConnectAction().execute(new ConnectEvent<ObjectConnection>(connection, null, null))
+                        workerProcess.onConnect(connection)
                     }
                 }
             }
+            one(acceptor).requestStop()
         }
 
         expectBlocks {
@@ -59,13 +65,16 @@ class DefaultWorkerProcessTest extends MultithreadedTestCase {
     }
 
     @Test
-    public void startThrowsExceptionOnConnectTimeout() {
+    public void startThrowsExceptionOnConnectTimeoutAndCleansUp() {
         expectAttachesListener()
+        ConnectionAcceptor acceptor = context.mock(ConnectionAcceptor.class)
+        workerProcess.startAccepting(acceptor)
 
         context.checking {
             one(execHandle).start()
             one(execHandle).getState()
             will(returnValue(ExecHandleState.STARTED))
+            one(acceptor).stop()
         }
 
         expectTimesOut(1, TimeUnit.SECONDS) {
@@ -79,9 +88,11 @@ class DefaultWorkerProcessTest extends MultithreadedTestCase {
     }
 
     @Test
-    public void startThrowsExceptionWhenChildProcessNeverConnects() {
+    public void startThrowsExceptionWhenChildProcessNeverConnectsAndCleansUp() {
         def listener = expectAttachesListener()
         def execResult = context.mock(ExecResult.class)
+        ConnectionAcceptor acceptor = context.mock(ConnectionAcceptor.class)
+        workerProcess.startAccepting(acceptor)
 
         context.checking {
             one(execHandle).start()
@@ -96,6 +107,7 @@ class DefaultWorkerProcessTest extends MultithreadedTestCase {
             will(returnValue(execResult))
             allowing(execResult).assertNormalExitValue()
             will(returnValue(execResult))
+            one(acceptor).stop()
         }
 
         expectBlocks {
@@ -109,10 +121,12 @@ class DefaultWorkerProcessTest extends MultithreadedTestCase {
     }
 
     @Test
-    public void startThrowsExceptionOnChildProcessFailure() {
+    public void startThrowsExceptionOnChildProcessFailureAndCleansUp() {
         def listener = expectAttachesListener()
         def failure = new RuntimeException('broken')
         ExecResult execResult = context.mock(ExecResult.class)
+        ConnectionAcceptor acceptor = context.mock(ConnectionAcceptor.class)
+        workerProcess.startAccepting(acceptor)
 
         context.checking {
             one(execHandle).start()
@@ -125,6 +139,7 @@ class DefaultWorkerProcessTest extends MultithreadedTestCase {
             }
             one(execResult).rethrowFailure()
             will(throwException(failure))
+            one(acceptor).stop()
         }
 
         expectBlocks {
@@ -142,12 +157,15 @@ class DefaultWorkerProcessTest extends MultithreadedTestCase {
         expectAttachesListener()
 
         ExecResult execResult = context.mock(ExecResult.class)
+        ConnectionAcceptor acceptor = context.mock(ConnectionAcceptor.class)
+        workerProcess.startAccepting(acceptor)
 
         context.checking {
             one(execHandle).start()
             will {
-                workerProcess.getConnectAction().execute(new ConnectEvent<ObjectConnection>(connection, null, null))
+                workerProcess.onConnect(connection)
             }
+            one(acceptor).requestStop()
         }
 
         workerProcess.start()
@@ -155,6 +173,7 @@ class DefaultWorkerProcessTest extends MultithreadedTestCase {
         context.checking {
             one(execHandle).waitForFinish()
             will(returnValue(execResult))
+            one(acceptor).stop()
             one(connection).stop()
             one(execResult).assertNormalExitValue()
         }
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ActionExecutionWorkerTest.java b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ActionExecutionWorkerTest.java
index 1cc3a33..d601f1f 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ActionExecutionWorkerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ActionExecutionWorkerTest.java
@@ -66,6 +66,8 @@ public class ActionExecutionWorkerTest {
             one(action).execute(with(notNullValue(WorkerProcessContext.class)));
             will(collectTo(collector));
 
+            one(connection).stop();
+
             one(messagingServices).stop();
         }});
 
@@ -96,6 +98,8 @@ public class ActionExecutionWorkerTest {
             one(action).execute(with(notNullValue(WorkerProcessContext.class)));
             will(throwException(failure));
 
+            one(connection).stop();
+
             one(messagingServices).stop();
         }});
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorkerTest.java b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorkerTest.java
index 9572957..5847570 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorkerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorkerTest.java
@@ -32,7 +32,7 @@ import java.net.URL;
 import java.util.Collection;
 import java.util.List;
 
-import static org.gradle.util.WrapUtil.*;
+import static org.gradle.util.WrapUtil.toList;
 
 @RunWith(JMock.class)
 public class ImplementationClassLoaderWorkerTest {
@@ -47,7 +47,6 @@ public class ImplementationClassLoaderWorkerTest {
     public void createsClassLoaderAndInstantiatesAndExecutesWorker() throws Exception {
         final Action<WorkerContext> action = context.mock(Action.class);
         final List<URL> implementationClassPath = toList(new File(".").toURI().toURL());
-
         Action<WorkerContext> serializableAction = helper.serializable(action, implementationClassLoader);
         ImplementationClassLoaderWorker worker = new TestImplementationClassLoaderWorker(LogLevel.DEBUG, toList("a", "b"), implementationClassPath, serializableAction);
 
@@ -66,7 +65,7 @@ public class ImplementationClassLoaderWorkerTest {
 
     private class TestImplementationClassLoaderWorker extends ImplementationClassLoaderWorker {
         private TestImplementationClassLoaderWorker(LogLevel logLevel, Collection<String> sharedPackages,
-                                    Collection<URL> implementationClassPath, Action<WorkerContext> workerAction) {
+                                                    Collection<URL> implementationClassPath, Action<WorkerContext> workerAction) {
             super(logLevel, sharedPackages, implementationClassPath, workerAction);
         }
 
@@ -77,7 +76,7 @@ public class ImplementationClassLoaderWorkerTest {
 
         @Override
         protected MutableURLClassLoader createImplementationClassLoader(ClassLoader system,
-                                                                           ClassLoader application) {
+                                                                        ClassLoader application) {
             return implementationClassLoader;
         }
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy
index 2491518..b6bf08c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy
@@ -16,17 +16,16 @@
 
 package org.gradle.process.internal.child
 
-import spock.lang.Specification
-import org.gradle.util.TemporaryFolder
-import org.junit.Rule
+import org.gradle.api.internal.classpath.ModuleRegistry
 import org.gradle.cache.CacheRepository
-import org.gradle.cache.CacheBuilder
-import org.gradle.cache.PersistentCache
 import org.gradle.cache.DirectoryCacheBuilder
-import org.gradle.api.internal.classpath.ModuleRegistry
+import org.gradle.cache.PersistentCache
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
 
 class WorkerProcessClassPathProviderTest extends Specification {
-    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final CacheRepository cacheRepository = Mock()
     final ModuleRegistry moduleRegistry = Mock()
     final WorkerProcessClassPathProvider provider = new WorkerProcessClassPathProvider(cacheRepository, moduleRegistry)
@@ -37,7 +36,7 @@ class WorkerProcessClassPathProviderTest extends Specification {
     }
 
     def createsTheWorkerClasspathOnDemand() {
-        def cacheDir = tmpDir.dir
+        def cacheDir = tmpDir.testDirectory
         def jarFile = cacheDir.file('gradle-worker.jar')
         DirectoryCacheBuilder cacheBuilder = Mock()
         PersistentCache cache = Mock()
@@ -57,7 +56,7 @@ class WorkerProcessClassPathProviderTest extends Specification {
     }
 
     def reusesTheCachedClasspath() {
-        def cacheDir = tmpDir.dir
+        def cacheDir = tmpDir.testDirectory
         def jarFile = cacheDir.file('gradle-worker.jar')
         DirectoryCacheBuilder cacheBuilder = Mock()
         PersistentCache cache = Mock()
diff --git a/subprojects/core/src/test/groovy/org/gradle/reporting/HtmlReportRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/reporting/HtmlReportRendererTest.groovy
index 8efd977..82d09c3 100644
--- a/subprojects/core/src/test/groovy/org/gradle/reporting/HtmlReportRendererTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/reporting/HtmlReportRendererTest.groovy
@@ -15,11 +15,11 @@
  */
 package org.gradle.reporting
 
-import spock.lang.Specification
-import org.w3c.dom.Element
-import org.junit.Rule
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.TextUtil
+import org.junit.Rule
+import org.w3c.dom.Element
+import spock.lang.Specification
 
 class HtmlReportRendererTest extends Specification {
     final DomReportRenderer<String> domRenderer = new DomReportRenderer<String>() {
@@ -28,7 +28,7 @@ class HtmlReportRendererTest extends Specification {
             parent.appendChild(parent.ownerDocument.createElement(model))
         }
     }
-    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final HtmlReportRenderer renderer = new HtmlReportRenderer()
 
     def "renders report to stream"() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/reporting/TextReportRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/reporting/TextReportRendererTest.groovy
index 5f9bc7d..ee003f7 100644
--- a/subprojects/core/src/test/groovy/org/gradle/reporting/TextReportRendererTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/reporting/TextReportRendererTest.groovy
@@ -15,12 +15,12 @@
  */
 package org.gradle.reporting
 
-import spock.lang.Specification
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
-import org.gradle.util.TemporaryFolder
+import spock.lang.Specification
 
 class TextReportRendererTest extends Specification {
-    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final TextReportRenderer<String> renderer = new TextReportRenderer<String>() {
         @Override protected void writeTo(String model, Writer out) {
             out.write("[")
diff --git a/subprojects/core/src/test/groovy/org/gradle/testfixtures/ProjectBuilderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/testfixtures/ProjectBuilderTest.groovy
index 45514b4..37fa260 100644
--- a/subprojects/core/src/test/groovy/org/gradle/testfixtures/ProjectBuilderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/testfixtures/ProjectBuilderTest.groovy
@@ -21,13 +21,13 @@ import org.gradle.api.Plugin
 import org.gradle.api.Project
 import org.gradle.api.internal.project.DefaultProject
 import org.gradle.api.tasks.TaskAction
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.Resources
-import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
 
 class ProjectBuilderTest extends Specification {
-    @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder()
+    @Rule public final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
     @Rule public final Resources resources = new Resources()
 
     def canCreateARootProject() {
@@ -48,17 +48,17 @@ class ProjectBuilderTest extends Specification {
 
     def canCreateARootProjectWithAGivenProjectDir() {
         when:
-        def project = ProjectBuilder.builder().withProjectDir(temporaryFolder.dir).build()
+        def project = ProjectBuilder.builder().withProjectDir(temporaryFolder.testDirectory).build()
 
         then:
-        project.projectDir == temporaryFolder.dir
+        project.projectDir == temporaryFolder.testDirectory
         project.gradle.gradleHomeDir == project.file('gradleHome')
         project.gradle.gradleUserHomeDir == project.file('userHome')
     }
 
     def canApplyACustomPluginByType() {
         when:
-        def project = ProjectBuilder.builder().withProjectDir(temporaryFolder.dir).build()
+        def project = ProjectBuilder.builder().withProjectDir(temporaryFolder.testDirectory).build()
         project.apply plugin: CustomPlugin
 
         then:
@@ -67,7 +67,7 @@ class ProjectBuilderTest extends Specification {
 
     def canApplyACustomPluginById() {
         when:
-        def project = ProjectBuilder.builder().withProjectDir(temporaryFolder.dir).build()
+        def project = ProjectBuilder.builder().withProjectDir(temporaryFolder.testDirectory).build()
         project.apply plugin: 'custom-plugin'
 
         then:
@@ -76,7 +76,7 @@ class ProjectBuilderTest extends Specification {
 
     def canCreateAndExecuteACustomTask() {
         when:
-        def project = ProjectBuilder.builder().withProjectDir(temporaryFolder.dir).build()
+        def project = ProjectBuilder.builder().withProjectDir(temporaryFolder.testDirectory).build()
         def task = project.task('custom', type: CustomTask)
         task.doStuff()
 
@@ -86,7 +86,7 @@ class ProjectBuilderTest extends Specification {
 
     def canApplyABuildScript() {
         when:
-        def project = ProjectBuilder.builder().withProjectDir(temporaryFolder.dir).build()
+        def project = ProjectBuilder.builder().withProjectDir(temporaryFolder.testDirectory).build()
         project.apply from: resources.getResource('ProjectBuilderTest.gradle')
 
         then:
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/ConfigureUtilTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/ConfigureUtilTest.groovy
index afd1979..e751714 100755
--- a/subprojects/core/src/test/groovy/org/gradle/util/ConfigureUtilTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/ConfigureUtilTest.groovy
@@ -17,83 +17,117 @@ package org.gradle.util
 
 import org.gradle.api.internal.ThreadGlobalInstantiator
 import org.gradle.util.ConfigureUtil.IncompleteInputException
-import org.junit.Test
+import spock.lang.Specification
+
 import static org.hamcrest.Matchers.equalTo
-import static org.hamcrest.Matchers.sameInstance
 import static org.junit.Assert.assertThat
-import static org.junit.Assert.fail
 
-class ConfigureUtilTest {
-    @Test
-    public void canConfigureObjectUsingClosure() {
+class ConfigureUtilTest extends Specification {
+    
+    def canConfigureObjectUsingClosure() {
+        given:
         List obj = []
         def cl = {
             add('a');
             assertThat(size(), equalTo(1));
             assertThat(obj, equalTo(['a']))
         }
+        
+        when:
         ConfigureUtil.configure(cl, obj)
-        assertThat(obj, equalTo(['a']))
+        
+        then:
+        obj == ['a']
     }
 
-    @Test
-    public void passesConfiguredObjectToClosureAsParameter() {
+    def passesConfiguredObjectToClosureAsParameter() {
+        given:
         List obj = []
         def cl = {
-            assertThat(it, sameInstance(obj))
+            it.is obj
         }
         def cl2 = {List list ->
-            assertThat(list, sameInstance(obj))
+            list.is obj
         }
         def cl3 = {->
-            assertThat(delegate, sameInstance(obj))
+            delegate.is obj
         }
+
+        when:
         ConfigureUtil.configure(cl, obj)
         ConfigureUtil.configure(cl2, obj)
         ConfigureUtil.configure(cl3, obj)
+
+        then:
+        noExceptionThrown()
     }
 
-    @Test
-    public void canConfigureObjectPropertyUsingMap() {
+    def canConfigureObjectPropertyUsingMap() {
+        given:
         Bean obj = new Bean()
 
+        when:
         ConfigureUtil.configureByMap(obj, prop: 'value')
-        assertThat(obj.prop, equalTo('value'))
 
+        then:
+        obj.prop == "value"
+
+        when:
         ConfigureUtil.configureByMap(obj, method: 'value2')
-        assertThat(obj.prop, equalTo('value2'))
+
+        then:
+        obj.prop == 'value2'
     }
 
-    @Test
-    public void canConfigureAndValidateObjectUsingMap() {
+    def canConfigureAndValidateObjectUsingMap() {
+        given:
         Bean obj = new Bean()
 
-        try {
-            //when
-            ConfigureUtil.configureByMap([prop: 'value'], obj, ['foo'])
-            //then
-            fail();
-        } catch (IncompleteInputException e) {
-            assert e.missingKeys.contains('foo')
-        }
+        when:
+        ConfigureUtil.configureByMap([prop: 'value'], obj, ['foo'])
 
-        //when
+        then:
+        def e = thrown(IncompleteInputException)
+        e.missingKeys.contains("foo")
+
+        when:
         ConfigureUtil.configureByMap([prop: 'value'], obj, ['prop'])
-        //then
+
+        then:
         assert obj.prop == 'value'
     }
 
-    @Test
-    public void throwsExceptionForUnknownProperty() {
+    def canConfigureAndValidateObjectUsingMapUsingGstrings() {
+        given:
         Bean obj = new Bean()
+        def prop = "prop"
+        def foo = "foo"
 
-        try {
-            ConfigureUtil.configureByMap(obj, unknown: 'value')
-            fail()
-        } catch (MissingPropertyException e) {
-            assertThat(e.type, equalTo(Bean.class))
-            assertThat(e.property, equalTo('unknown'))
-        }
+        when:
+        ConfigureUtil.configureByMap(["$prop": 'value'], obj, ["$foo"])
+
+        then:
+        def e = thrown(IncompleteInputException)
+        e.missingKeys.contains("foo")
+
+        when:
+        ConfigureUtil.configureByMap(["$prop": 'value'], obj, ["$prop"])
+
+        then:
+        assert obj.prop == 'value'
+    }
+
+    def throwsExceptionForUnknownProperty() {
+        given:
+        Bean obj = new Bean()
+
+        when:
+        ConfigureUtil.configureByMap(obj, unknown: 'value')
+
+        then:
+        def e = thrown(MissingPropertyException)
+        e.type == Bean
+        e.property == 'unknown'
     }
     
     static class TestConfigurable implements Configurable {
@@ -105,18 +139,27 @@ class ConfigureUtilTest {
         }
     }
     
-    @Test
-    void testConfigurableAware() {
+
+    def testConfigurableAware() {
+        given:
         def c = new TestConfigurable()
+
+        when:
         ConfigureUtil.configure({ a = 1 }, c)
-        assert c.props.a == 1
+
+        then:
+        c.props.a == 1
     }
-    
-    @Test
+
     void configureByMapTriesMethodForExtensibleObjects() {
+        given:
         Bean bean = ThreadGlobalInstantiator.getOrCreate().newInstance(Bean)
+
+        when:
         ConfigureUtil.configureByMap(bean, method:  "foo")
-        assert bean.prop == "foo"
+
+        then:
+        bean.prop == "foo"
     }
     
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/GFileUtilsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/GFileUtilsTest.groovy
index bc1a8da..e241b24 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/GFileUtilsTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/GFileUtilsTest.groovy
@@ -16,19 +16,20 @@
 
 package org.gradle.util
 
+import org.gradle.api.UncheckedIOException
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 import static org.gradle.util.GFileUtils.mkdirs
 import static org.gradle.util.GFileUtils.parentMkdirs
-import org.gradle.api.UncheckedIOException
 
 /**
  * by Szczepan Faber, created at: 2/28/12
  */
 class GFileUtilsTest extends Specification {
 
-    @Rule TemporaryFolder temp
+    @Rule TestNameTestDirectoryProvider temp
 
     def "can read the file's tail"() {
         def f = temp.file("foo.txt") << """
@@ -45,12 +46,12 @@ three
 """
     }
 
-    def "createDirectory() succeeds if directory already exists"() {
+    def "mkdirs succeeds if directory already exists"() {
         def dir = temp.createDir("foo")
         assert dir.exists()
 
         when:
-        GFileUtils.createDirectory(dir)
+        GFileUtils.mkdirs(dir)
 
         then:
         noExceptionThrown()
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy
index 70ac33d..c0de292 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy
@@ -213,19 +213,6 @@ class GradleVersionTest extends Specification {
         '0.9'                     | '0.9-20101220100000'
     }
 
-    def canCompareWithNonSymbolicVersions() {
-        expect:
-        GradleVersion.version(a) > GradleVersion.version(b)
-        GradleVersion.version(b) < GradleVersion.version(a)
-        GradleVersion.version(a) == GradleVersion.version(a)
-        GradleVersion.version(b) == GradleVersion.version(b)
-
-        where:
-        a                         | b
-        '0.0-20101220110000+0100' | '1.0'
-        '0.0'                     | '0.9.2'
-    }
-
     def "can get version base"() {
         expect:
         GradleVersion.version(v).versionBase == base
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/MatchersTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/MatchersTest.groovy
new file mode 100644
index 0000000..7c7dbbe
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/util/MatchersTest.groovy
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.util
+
+import spock.lang.Specification
+
+import java.util.regex.Pattern
+
+import static org.gradle.util.Matchers.matchesRegexp
+
+/**
+ * by Szczepan Faber, created at: 12/17/12
+ */
+class MatchersTest extends Specification {
+
+    def "matches regex"() {
+        expect:
+        matchesRegexp("foo.*").matches(match)
+
+        and:
+        !matchesRegexp(Pattern.compile("foo.*")).matches(noMatch)
+
+        where:
+        match     | noMatch
+        "foo"     | "fo"
+        "foo bar" | " foo"
+        "fooo"    | "xxxx"
+    }
+}
diff --git a/subprojects/core/src/test/resources/org/gradle/api/internal/xml-transformer-test.dtd b/subprojects/core/src/test/resources/org/gradle/api/internal/xml/xml-transformer-test.dtd
similarity index 100%
copy from subprojects/core/src/test/resources/org/gradle/api/internal/xml-transformer-test.dtd
copy to subprojects/core/src/test/resources/org/gradle/api/internal/xml/xml-transformer-test.dtd
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy
index a82b1ca..d6f57ef 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy
@@ -32,10 +32,10 @@ import org.gradle.api.internal.tasks.TaskExecuter
 import org.gradle.api.internal.tasks.TaskStateInternal
 import org.gradle.api.specs.Spec
 import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.GUtil
 import org.gradle.util.HelperUtil
 import org.gradle.util.Matchers
-import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -49,7 +49,7 @@ import static org.junit.Assert.assertFalse
 public abstract class AbstractSpockTaskTest extends Specification {
     public static final String TEST_TASK_NAME = "taskname"
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder()
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
     private AbstractProject project = HelperUtil.createRootProject()
 
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractTaskTest.java b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractTaskTest.java
index 7f6db32..329f2de 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractTaskTest.java
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractTaskTest.java
@@ -23,32 +23,30 @@ import org.gradle.api.Project;
 import org.gradle.api.Task;
 import org.gradle.api.internal.AbstractTask;
 import org.gradle.api.internal.Actions;
-import org.gradle.api.internal.AsmBackedClassGenerator;
 import org.gradle.api.internal.DependencyInjectingInstantiator;
 import org.gradle.api.internal.project.AbstractProject;
 import org.gradle.api.internal.project.DefaultProject;
 import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory;
-import org.gradle.api.internal.project.taskfactory.TaskFactory;
+import org.gradle.api.internal.project.taskfactory.ITaskFactory;
 import org.gradle.api.internal.tasks.TaskExecuter;
 import org.gradle.api.internal.tasks.TaskStateInternal;
 import org.gradle.api.specs.Spec;
-import org.gradle.internal.reflect.DirectInstantiator;
 import org.gradle.internal.reflect.Instantiator;
-import org.gradle.internal.reflect.ObjectInstantiationException;
 import org.gradle.internal.service.DefaultServiceRegistry;
-import org.gradle.util.*;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
+import org.gradle.util.GUtil;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.JUnit4GroovyMockery;
 import org.jmock.Expectations;
 import org.jmock.lib.legacy.ClassImposteriser;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import spock.lang.Issue;
 
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import static org.gradle.util.Matchers.dependsOn;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.Assert.*;
 
 /**
@@ -57,7 +55,7 @@ import static org.junit.Assert.*;
 public abstract class AbstractTaskTest {
     public static final String TEST_TASK_NAME = "taskname";
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
     protected JUnit4GroovyMockery context = new JUnit4GroovyMockery() {{
         setImposteriser(ClassImposteriser.INSTANCE);
@@ -69,8 +67,6 @@ public abstract class AbstractTaskTest {
 
     private AbstractProject project = HelperUtil.createRootProject();
 
-    private final AnnotationProcessingTaskFactory rootFactory = new AnnotationProcessingTaskFactory(new TaskFactory(new AsmBackedClassGenerator()));
-
     public abstract AbstractTask getTask();
 
     public <T extends AbstractTask> T createTask(Class<T> type) {
@@ -82,9 +78,7 @@ public abstract class AbstractTaskTest {
     }
 
     public <T extends AbstractTask> T createTask(Class<T> type, ProjectInternal project, String name) {
-        DefaultServiceRegistry registry = new DefaultServiceRegistry();
-        registry.add(Instantiator.class, new DirectInstantiator());
-        Task task = rootFactory.createChild(project, instantiator).createTask(GUtil.map(Task.TASK_TYPE, type, Task.TASK_NAME, name));
+        Task task = project.getServices().get(ITaskFactory.class).createTask(GUtil.map(Task.TASK_TYPE, type, Task.TASK_NAME, name));
         assertTrue(type.isAssignableFrom(task.getClass()));
         return type.cast(task);
     }
@@ -124,19 +118,6 @@ public abstract class AbstractTaskTest {
     }
 
     @Test
-    public void testDependsOn() {
-        Task dependsOnTask = createTask(project, "somename");
-        Task task = createTask(project, TEST_TASK_NAME);
-        project.getTasks().add("path1");
-        project.getTasks().add("path2");
-
-        task.dependsOn(Project.PATH_SEPARATOR + "path1");
-        assertThat(task, dependsOn("path1"));
-        task.dependsOn("path2", dependsOnTask);
-        assertThat(task, dependsOn("path1", "path2", "somename"));
-    }
-
-    @Test
     public void testToString() {
         assertEquals("task '" + getTask().getPath() + "'", getTask().toString());
     }
@@ -163,7 +144,7 @@ public abstract class AbstractTaskTest {
         final TaskExecuter executer = context.mock(TaskExecuter.class);
         task.setExecuter(executer);
 
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             one(executer).execute(with(sameInstance(task)), with(notNullValue(TaskStateInternal.class)));
         }});
 
@@ -267,39 +248,4 @@ public abstract class AbstractTaskTest {
         condition1.set(true);
         assertTrue(task.getOnlyIf().isSatisfiedBy(task));
     }
-
-    @Test
-    public void testDependentTaskDidWork() {
-        final Task task1 = context.mock(Task.class, "task1");
-        final Task task2 = context.mock(Task.class, "task2");
-        final TaskDependency dependencyMock = context.mock(TaskDependency.class);
-        getTask().dependsOn(dependencyMock);
-        context.checking(new Expectations() {{
-            allowing(dependencyMock).getDependencies(getTask()); will(returnValue(WrapUtil.toSet(task1, task2)));
-
-            exactly(2).of(task1).getDidWork();
-            will(returnValue(false));
-
-            exactly(2).of(task2).getDidWork();
-            will(onConsecutiveCalls(returnValue(false), returnValue(true)));
-        }});
-
-        assertFalse(getTask().dependsOnTaskDidWork());
-
-        assertTrue(getTask().dependsOnTaskDidWork());
-    }
-
-    @Test
-    @Issue("http://issues.gradle.org/browse/GRADLE-2022")
-    public void testGoodErrorMessageWhenTaskInstantiatedDirectly() {
-        try {
-            instantiator.newInstance(getTask().getClass());
-            throw new RuntimeException("Direct instantiation of " + getTask().getClass() + " should have produced an exception");
-        } catch (ObjectInstantiationException e) {
-            // compared to direct instantiation, instantiator (which we use to get any ctor args injected) wraps TaskInstantiationException, so unwrap
-            Throwable cause = e.getCause();
-            assertEquals(TaskInstantiationException.class, cause.getClass());
-            assertThat(cause.getMessage(), containsString("has been instantiated directly which is not supported"));
-        }
-    }
 }
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTaskTest.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTaskTest.groovy
index c802f3b..be42d17 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTaskTest.groovy
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTaskTest.groovy
@@ -20,7 +20,9 @@ import org.gradle.api.internal.ConventionTask
 import org.gradle.api.internal.file.FileResolver
 import org.gradle.api.tasks.AbstractConventionTaskTest
 import org.junit.Test
-import static org.junit.Assert.*
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertTrue
 
 /**
  * @author Hans Dockter
@@ -44,7 +46,7 @@ abstract class AbstractArchiveTaskTest extends AbstractConventionTaskTest {
         archiveTask.appendix = 'testappendix'
         archiveTask.version = '1.0'
         archiveTask.classifier = 'src'
-        archiveTask.destinationDir = new File(tmpDir.dir, 'destinationDir')
+        archiveTask.destinationDir = new File(tmpDir.testDirectory, 'destinationDir')
     }
 
     @Test public void testExecute() {
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/test/fixtures/ConcurrentTestUtil.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/test/fixtures/ConcurrentTestUtil.groovy
new file mode 100644
index 0000000..d16ee8d
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/test/fixtures/ConcurrentTestUtil.groovy
@@ -0,0 +1,772 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.test.fixtures
+
+import org.gradle.internal.concurrent.ExecutorFactory
+import org.gradle.internal.concurrent.StoppableExecutor
+import org.junit.rules.ExternalResource
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+import java.util.concurrent.CopyOnWriteArraySet
+import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.locks.Condition
+import java.util.concurrent.locks.Lock
+import java.util.concurrent.locks.ReentrantLock
+
+/**
+ * <p>A base class for writing specifications which exercise concurrent code.
+ *
+ * <p>See {@link org.gradle.util.ConcurrentSpecificationTest} for some examples.
+ *
+ * <p>Provides {@link Executor} and {@link ExecutorFactory} implementations for use during the test. These provide real concurrency.
+ * The test threads are cleaned up at the end of the test, and any exceptions thrown by those tests are propagated.
+ *
+ * <p>Provides some fixtures for testing:</p>
+ *
+ * <ul>
+ * <li>An action starts another action asynchronously without waiting for the result.</li>
+ * <li>An action starts another action asynchronously and waits for the result.</li>
+ * </ul>
+ */
+class ConcurrentTestUtil extends ExternalResource {
+    private static final Logger LOG = LoggerFactory.getLogger(ConcurrentTestUtil.class)
+
+    private Lock lock = new ReentrantLock()
+    private Condition threadsChanged = lock.newCondition()
+    private Set<TestThread> threads = [] as Set
+    private Closure failureHandler
+    private List<Throwable> failures = []
+    private timeout = 5000
+
+    ConcurrentTestUtil() {}
+
+    ConcurrentTestUtil(int timeout) {
+        this.timeout = timeout
+    }
+
+    @Override
+    protected void after() {
+        finished()
+    }
+
+    //simplistic polling assertion. attempts asserting every x millis up to some max timeout
+    static void poll(int timeout = 10, Closure assertion) {
+        def expiry = System.currentTimeMillis() + timeout * 1000 // convert to ms
+        while(true) {
+            try {
+                assertion()
+                return
+            } catch (Throwable t) {
+                if (System.currentTimeMillis() > expiry) {
+                    throw t
+                }
+                Thread.sleep(100);
+            }
+        }
+    }
+
+    void setShortTimeout(int millis) {
+        this.timeout = millis
+    }
+
+    ExecutorFactory getExecutorFactory() {
+        return new ExecutorFactory() {
+            StoppableExecutor create(String displayName) {
+                return new StoppableExecutorStub(ConcurrentTestUtil.this)
+            }
+        }
+    }
+
+    Executor getExecutor() {
+        return new Executor() {
+            void execute(Runnable runnable) {
+                startThread(runnable)
+            }
+        }
+    }
+
+    TestThread startThread(Runnable cl) {
+        lock.lock()
+        try {
+            TestThread thread = new TestThread(this, lock, cl)
+            thread.start()
+            return thread
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    /**
+     * Starts a thread which executes the given action/closure. Does not wait for the thread to complete.
+     *
+     * @return A handle to the test thread.
+     */
+    TestParticipant start(Runnable cl) {
+        lock.lock()
+        try {
+            TestThread thread = new TestThread(this, lock, cl)
+            thread.start()
+            return new TestParticipantImpl(this, thread)
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    /**
+     * Creates a new asynchronous action.
+     */
+    StartAsyncAction startsAsyncAction() {
+        return new StartAsyncAction(this)
+    }
+
+    /**
+     * Creates a new blocking action.
+     */
+    WaitForAsyncCallback waitsForAsyncCallback() {
+        return new WaitForAsyncCallback(this)
+    }
+
+    /**
+     * Creates a new action which waits until an async. action is complete.
+     */
+    WaitForAsyncAction waitsForAsyncActionToComplete() {
+        return new WaitForAsyncAction(this)
+    }
+
+    /**
+     * Returns a composite participant, which you can use to perform atomic operations on.
+     *
+     * @return A handle to the composite participant.
+     */
+    TestParticipant all(TestParticipant... participants) {
+        return new CompositeTestParticipant(this, lock, participants as List)
+    }
+
+    void onFailure(Closure cl) {
+        lock.lock()
+        try {
+            failureHandler = cl
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    private void onFailure(Throwable t) {
+        lock.lock()
+        try {
+            if (failureHandler != null) {
+                failureHandler.call(t)
+            } else {
+                failures << t
+            }
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    /**
+     * Waits for all threads to complete. Asserts that the threads complete in a 'short' time. Rethrows any exceptions thrown by test threads.
+     */
+    void finished() {
+        Date timeout = shortTimeout()
+        lock.lock()
+        try {
+            LOG.info("Waiting for test threads to complete.")
+            while (!threads.isEmpty()) {
+                if (!threadsChanged.awaitUntil(timeout)) {
+                    onFailure(new IllegalStateException("Timeout waiting for test threads to complete."))
+                    break;
+                }
+            }
+            threads.each { thread ->
+                thread.interrupt()
+            }
+
+            LOG.info("Finishing up.")
+            if (!failures.isEmpty()) {
+                throw failures[0]
+            }
+        } finally {
+            failureHandler = null
+            threads.clear()
+            failures.clear()
+            lock.unlock()
+        }
+
+    }
+
+    Date shortTimeout() {
+        return new Date(System.currentTimeMillis() + timeout)
+    }
+
+    void run(Closure cl, Date timeout) {
+        def thread = new TestThread(this, lock, cl)
+        thread.start()
+        thread.completesBefore(timeout)
+    }
+
+    void onThreadStart(TestThread thread) {
+        lock.lock()
+        try {
+            threads << thread
+            threadsChanged.signalAll()
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    void onThreadComplete(TestThread thread, Throwable failure) {
+        lock.lock()
+        try {
+            threads.remove(thread)
+            if (failure) {
+                onFailure(failure)
+            }
+            threadsChanged.signalAll()
+        } finally {
+            lock.unlock()
+        }
+    }
+}
+
+class TestThread extends Thread {
+    private static final Logger LOG = LoggerFactory.getLogger(TestThread.class)
+    private final ConcurrentTestUtil owner
+    private final Runnable action
+    private final Lock lock
+    private final Condition stateChanged
+    private boolean complete
+
+    TestThread(ConcurrentTestUtil owner, Lock lock, Runnable action) {
+        this.owner = owner
+        this.action = action
+        this.lock = lock
+        this.stateChanged = lock.newCondition()
+    }
+
+    @Override
+    void start() {
+        LOG.info("$this started.")
+
+        lock.lock()
+        try {
+            owner.onThreadStart(this)
+            stateChanged.signalAll()
+        } finally {
+            lock.unlock()
+        }
+
+        super.start()
+    }
+
+    void running() {
+        lock.lock()
+        try {
+            if (complete) {
+                throw new IllegalStateException("$this should still be running, but is not.")
+            }
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    void completesBefore(Date timeout) {
+        lock.lock()
+        try {
+            LOG.info("Waiting for $this to complete.")
+            while (!complete) {
+                if (!stateChanged.awaitUntil(timeout)) {
+                    interrupt()
+                    throw new IllegalStateException("Timeout waiting for $this to complete.")
+                }
+            }
+            LOG.info("$this completed.")
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    @Override
+    void run() {
+        Throwable failure = null
+        try {
+            action.run()
+        } catch (Throwable t) {
+            failure = t
+        }
+
+        lock.lock()
+        try {
+            complete = true
+            stateChanged.signalAll()
+            owner.onThreadComplete(this, failure)
+            LOG.info("$this completed.")
+        } finally {
+            lock.unlock()
+        }
+    }
+}
+
+/**
+ * Some potentially long running operation.
+ */
+interface LongRunningAction {
+    /**
+     * Blocks until this action has completed. Asserts that the action completes in a 'short' time. Rethrows any exception from the action.
+     */
+    void completed()
+
+    /**
+     * Blocks until this action has completed. Asserts that the action completes within the specified time. Rethrows any exception from the action.
+     */
+    void completesWithin(long maxWaitValue, TimeUnit maxWaitUnits)
+
+    /**
+     * Blocks until this action has completed. Asserts that the action completes before the given time. Rethrows any exception from the action.
+     */
+    void completesBefore(Date timeout)
+}
+
+interface TestParticipant extends LongRunningAction {
+    /**
+     * Asserts that this test participant is running.
+     */
+    void running()
+}
+
+abstract class AbstractAction implements LongRunningAction {
+    
+    Date defaultExpiry
+
+    AbstractAction(Date defaultExpiry) {
+        this.defaultExpiry = defaultExpiry
+    }
+    
+    void completed() {
+        completesBefore(defaultExpiry)
+    }
+
+    void completesWithin(long maxWaitValue, TimeUnit maxWaitUnits) {
+        Date expiry = new Date(System.currentTimeMillis() + maxWaitUnits.toMillis(maxWaitValue))
+        completesBefore(expiry + 500)
+    }
+
+    abstract void completesBefore(Date timeout)
+}
+
+abstract class AbstractTestParticipant extends AbstractAction implements TestParticipant {
+    private final ConcurrentTestUtil owner
+
+    AbstractTestParticipant(ConcurrentTestUtil owner) {
+        super(owner.shortTimeout())
+        this.owner = owner
+    }
+}
+
+class TestParticipantImpl extends AbstractTestParticipant {
+    private final TestThread thread
+
+    TestParticipantImpl(ConcurrentTestUtil owner, TestThread thread) {
+        super(owner)
+        this.thread = thread
+    }
+
+    @Override
+    void completesBefore(Date timeout) {
+        thread.completesBefore(timeout)
+    }
+
+    void running() {
+        thread.running()
+    }
+}
+
+class CompositeTestParticipant extends AbstractTestParticipant {
+    private final List<TestParticipant> participants
+    private final Lock lock
+
+    CompositeTestParticipant(ConcurrentTestUtil owner, Lock lock, List<TestParticipant> participants) {
+        super(owner)
+        this.participants = participants
+        this.lock = lock
+    }
+
+    void running() {
+        lock.lock()
+        try {
+            participants*.running()
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    @Override
+    void completesBefore(Date timeout) {
+        lock.lock()
+        try {
+            participants*.completesBefore(timeout)
+        } finally {
+            lock.unlock()
+        }
+    }
+}
+
+class StoppableExecutorStub implements StoppableExecutor {
+    final ConcurrentTestUtil owner
+    final Set<TestThread> threads = new CopyOnWriteArraySet<TestThread>()
+
+    StoppableExecutorStub(ConcurrentTestUtil owner) {
+        this.owner = owner
+    }
+
+    void stop() {
+        def timeout = owner.shortTimeout()
+        threads.each { it.completesBefore(timeout) }
+    }
+
+    void stop(int timeoutValue, TimeUnit timeoutUnits) {
+        throw new UnsupportedOperationException()
+    }
+
+    void requestStop() {
+        throw new UnsupportedOperationException()
+    }
+
+    void execute(Runnable runnable) {
+        threads.add(owner.startThread(runnable))
+    }
+}
+
+class AbstractAsyncAction {
+    protected final ConcurrentTestUtil owner
+    private final Lock lock = new ReentrantLock()
+    protected final Condition condition = lock.newCondition()
+    protected Throwable failure
+
+    AbstractAsyncAction(ConcurrentTestUtil owner) {
+        this.owner = owner
+    }
+
+    protected Date shortTimeout() {
+        return owner.shortTimeout()
+    }
+
+    protected void onFailure(Throwable throwable) {
+        withLock {
+            failure = throwable
+            condition.signalAll()
+        }
+    }
+
+    protected def withLock(Closure cl) {
+        lock.lock()
+        try {
+            return cl.call()
+        } finally {
+            lock.unlock()
+        }
+    }
+}
+
+class StartAsyncAction extends AbstractAsyncAction {
+    private boolean started
+    private boolean completed
+    private Thread startThread
+
+    StartAsyncAction(ConcurrentTestUtil owner) {
+        super(owner)
+    }
+
+    /**
+     * Runs the given action, and then waits until another another thread calls {@link #done()}.  Asserts that the start action does not block waiting for
+     * the async action to complete.
+     *
+     * @param action The start action
+     * @return this
+     */
+    StartAsyncAction started(Runnable action) {
+        owner.onFailure this.&onFailure
+        doStart(action)
+        waitForStartToComplete()
+        waitForFinish()
+        return this
+    }
+
+    /**
+     * Marks that the async. action is now finished.
+     */
+    void done() {
+        waitForStartToComplete()
+        doFinish()
+    }
+
+
+    private void doStart(Runnable action) {
+        owner.startThread {
+            withLock {
+                if (startThread != null) {
+                    throw new IllegalStateException("Cannot start action multiple times.")
+                }
+                startThread = Thread.currentThread()
+                condition.signalAll()
+            }
+
+            action.run()
+
+            withLock {
+                started = true
+                condition.signalAll()
+            }
+        }
+
+        withLock {
+            while (startThread == null) {
+                condition.await()
+            }
+        }
+    }
+
+    private void doFinish() {
+        withLock {
+            if (completed) {
+                throw new IllegalStateException("Cannot run async action multiple times.")
+            }
+            completed = true
+            condition.signalAll()
+        }
+    }
+
+    private void waitForStartToComplete() {
+        Date timeout = shortTimeout()
+        withLock {
+            if (startThread == null) {
+                def e = new IllegalStateException("Action has not been started.")
+                e.printStackTrace()
+                throw e
+            }
+            if (Thread.currentThread() == startThread) {
+                def e = new IllegalStateException("Cannot wait for action to complete from the thread that is executing it.")
+                e.printStackTrace()
+                throw e
+            }
+            while (!started && !failure) {
+                if (!condition.awaitUntil(timeout)) {
+                    throw new IllegalStateException("Expected action to complete quickly, but it did not.")
+                }
+            }
+            if (failure) {
+                throw failure
+            }
+        }
+    }
+
+    private void waitForFinish() {
+        Date timeout = shortTimeout()
+        withLock {
+            while (!completed && !failure) {
+                if (!condition.awaitUntil(timeout)) {
+                    throw new IllegalStateException("Expected async action to complete, but it did not.")
+                }
+            }
+            if (failure) {
+                throw failure
+            }
+        }
+    }
+}
+
+abstract class AbstractWaitAction extends AbstractAsyncAction {
+    protected boolean started
+    protected boolean completed
+
+    AbstractWaitAction(ConcurrentTestUtil owner) {
+        super(owner)
+    }
+
+    protected void waitForBlockingActionToComplete() {
+        Date expiry = shortTimeout()
+        withLock {
+            while (!completed && !failure) {
+                if (!condition.awaitUntil(expiry)) {
+                    throw new IllegalStateException("Expected action to unblock, but it did not.")
+                }
+            }
+            if (failure) {
+                throw failure
+            }
+        }
+    }
+
+    protected void startBlockingAction(Runnable action) {
+        owner.startThread {
+            withLock {
+                started = true
+                condition.signalAll()
+            }
+
+            action.run()
+
+            withLock {
+                completed = true
+                condition.signalAll()
+            }
+        }
+
+        withLock {
+            while (!started) {
+                condition.await()
+            }
+        }
+    }
+
+    protected void assertBlocked() {
+        withLock {
+            if (completed) {
+                throw new IllegalStateException("Expected action to block, but it did not.")
+            }
+        }
+    }
+}
+
+class WaitForAsyncCallback extends AbstractWaitAction {
+    private boolean callbackCompleted
+    private Runnable callback
+
+    WaitForAsyncCallback(ConcurrentTestUtil owner) {
+        super(owner)
+    }
+
+    /**
+     * Runs the given action. Asserts that it blocks until after asynchronous callback is made. The action must register the callback using {@link #callbackLater(Runnable)}.
+     */
+    WaitForAsyncCallback start(Runnable action) {
+        owner.onFailure this.&onFailure
+
+        startBlockingAction(action)
+        waitForCallbackToBeRegistered()
+
+        Thread.sleep(500)
+
+        assertBlocked()
+        runCallbackAction()
+        waitForBlockingActionToComplete()
+
+        return this
+    }
+
+    /**
+     * Registers the callback which will unblock the action.
+     */
+    public void callbackLater(Runnable action) {
+        withLock {
+            if (callback) {
+                throw new IllegalStateException("Cannot register callback action multiple times.")
+            }
+            if (!started) {
+                throw new IllegalStateException("Action has not been started.")
+            }
+            callback = action
+            condition.signalAll()
+        }
+    }
+
+    private def runCallbackAction() {
+        owner.startThread {
+            callback.run()
+
+            withLock {
+                callbackCompleted = true
+                condition.signalAll()
+            }
+        }
+
+        Date timeout = shortTimeout()
+        withLock {
+            while (!callbackCompleted && !failure) {
+                if (!condition.awaitUntil(timeout)) {
+                    throw new IllegalStateException("Expected callback action to complete, but it did not.")
+                }
+            }
+            if (failure) {
+                throw failure
+            }
+        }
+    }
+
+    private void waitForCallbackToBeRegistered() {
+        Date expiry = shortTimeout()
+        withLock {
+            while (!callback && !failure && !completed) {
+                if (!condition.awaitUntil(expiry)) {
+                    throw new IllegalStateException("Expected action to register a callback action, but it did not.")
+                }
+            }
+            if (failure) {
+                throw failure
+            }
+            if (completed) {
+                throw new IllegalStateException("Expected action to block, but it did not.")
+            }
+        }
+    }
+}
+
+class WaitForAsyncAction extends AbstractWaitAction {
+    boolean asyncActionComplete
+
+    WaitForAsyncAction(ConcurrentTestUtil owner) {
+        super(owner)
+    }
+
+    WaitForAsyncAction start(Runnable action) {
+        owner.onFailure this.&onFailure
+        startBlockingAction(action)
+        waitForAsyncAction()
+        waitForBlockingActionToComplete()
+        return this
+    }
+
+    WaitForAsyncAction done() {
+        Thread.sleep(500)
+        assertBlocked()
+
+        withLock {
+            asyncActionComplete = true
+            condition.signalAll()
+        }
+
+        return this
+    }
+
+    def waitForAsyncAction() {
+        Date expiry = shortTimeout()
+        withLock {
+            while (!asyncActionComplete && !completed && !failure) {
+                if (!condition.awaitUntil(expiry)) {
+                    throw new IllegalStateException("Expected async action to be started, but it was not.")
+                }
+            }
+            if (failure) {
+                throw failure
+            }
+            if (!asyncActionComplete && completed) {
+                throw new IllegalStateException("Expected action to block, but it did not.")
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/tests/fixtures/ConcurrentTestUtil.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/tests/fixtures/ConcurrentTestUtil.groovy
deleted file mode 100644
index 502be67..0000000
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/tests/fixtures/ConcurrentTestUtil.groovy
+++ /dev/null
@@ -1,771 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.tests.fixtures
-
-import java.util.concurrent.CopyOnWriteArraySet
-import java.util.concurrent.Executor
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.locks.Condition
-import java.util.concurrent.locks.Lock
-import java.util.concurrent.locks.ReentrantLock
-import org.gradle.internal.concurrent.ExecutorFactory
-import org.gradle.internal.concurrent.StoppableExecutor
-import org.junit.rules.ExternalResource
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-
-/**
- * <p>A base class for writing specifications which exercise concurrent code.
- *
- * <p>See {@link org.gradle.util.ConcurrentSpecificationTest} for some examples.
- *
- * <p>Provides {@link Executor} and {@link ExecutorFactory} implementations for use during the test. These provide real concurrency.
- * The test threads are cleaned up at the end of the test, and any exceptions thrown by those tests are propagated.
- *
- * <p>Provides some fixtures for testing:</p>
- *
- * <ul>
- * <li>An action starts another action asynchronously without waiting for the result.</li>
- * <li>An action starts another action asynchronously and waits for the result.</li>
- * </ul>
- */
-class ConcurrentTestUtil extends ExternalResource {
-    private static final Logger LOG = LoggerFactory.getLogger(ConcurrentTestUtil.class)
-
-    private Lock lock = new ReentrantLock()
-    private Condition threadsChanged = lock.newCondition()
-    private Set<TestThread> threads = [] as Set
-    private Closure failureHandler
-    private List<Throwable> failures = []
-    private timeout = 5000
-
-    ConcurrentTestUtil() {}
-
-    ConcurrentTestUtil(int timeout) {
-        this.timeout = timeout
-    }
-
-    @Override
-    protected void after() {
-        finished()
-    }
-
-    //simplistic polling assertion. attempts asserting every x millis up to some max timeout
-    static void poll(int timeout = 10, Closure assertion) {
-        def expiry = System.currentTimeMillis() + timeout * 1000 // convert to ms
-        while(true) {
-            try {
-                assertion()
-                return
-            } catch (Throwable t) {
-                if (System.currentTimeMillis() > expiry) {
-                    throw t
-                }
-                Thread.sleep(100);
-            }
-        }
-    }
-
-    void setShortTimeout(int millis) {
-        this.timeout = millis
-    }
-
-    ExecutorFactory getExecutorFactory() {
-        return new ExecutorFactory() {
-            StoppableExecutor create(String displayName) {
-                return new StoppableExecutorStub(ConcurrentTestUtil.this)
-            }
-        }
-    }
-
-    Executor getExecutor() {
-        return new Executor() {
-            void execute(Runnable runnable) {
-                startThread(runnable)
-            }
-        }
-    }
-
-    TestThread startThread(Runnable cl) {
-        lock.lock()
-        try {
-            TestThread thread = new TestThread(this, lock, cl)
-            thread.start()
-            return thread
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    /**
-     * Starts a thread which executes the given action/closure. Does not wait for the thread to complete.
-     *
-     * @return A handle to the test thread.
-     */
-    TestParticipant start(Runnable cl) {
-        lock.lock()
-        try {
-            TestThread thread = new TestThread(this, lock, cl)
-            thread.start()
-            return new TestParticipantImpl(this, thread)
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    /**
-     * Creates a new asynchronous action.
-     */
-    StartAsyncAction startsAsyncAction() {
-        return new StartAsyncAction(this)
-    }
-
-    /**
-     * Creates a new blocking action.
-     */
-    WaitForAsyncCallback waitsForAsyncCallback() {
-        return new WaitForAsyncCallback(this)
-    }
-
-    /**
-     * Creates a new action which waits until an async. action is complete.
-     */
-    WaitForAsyncAction waitsForAsyncActionToComplete() {
-        return new WaitForAsyncAction(this)
-    }
-
-    /**
-     * Returns a composite participant, which you can use to perform atomic operations on.
-     *
-     * @return A handle to the composite participant.
-     */
-    TestParticipant all(TestParticipant... participants) {
-        return new CompositeTestParticipant(this, lock, participants as List)
-    }
-
-    void onFailure(Closure cl) {
-        lock.lock()
-        try {
-            failureHandler = cl
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    private void onFailure(Throwable t) {
-        lock.lock()
-        try {
-            if (failureHandler != null) {
-                failureHandler.call(t)
-            } else {
-                failures << t
-            }
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    /**
-     * Waits for all threads to complete. Asserts that the threads complete in a 'short' time. Rethrows any exceptions thrown by test threads.
-     */
-    void finished() {
-        Date timeout = shortTimeout()
-        lock.lock()
-        try {
-            LOG.info("Waiting for test threads to complete.")
-            while (!threads.isEmpty()) {
-                if (!threadsChanged.awaitUntil(timeout)) {
-                    onFailure(new IllegalStateException("Timeout waiting for test threads to complete."))
-                    break;
-                }
-            }
-            threads.each { thread ->
-                thread.interrupt()
-            }
-
-            LOG.info("Finishing up.")
-            if (!failures.isEmpty()) {
-                throw failures[0]
-            }
-        } finally {
-            failureHandler = null
-            threads.clear()
-            failures.clear()
-            lock.unlock()
-        }
-
-    }
-
-    Date shortTimeout() {
-        return new Date(System.currentTimeMillis() + timeout)
-    }
-
-    void run(Closure cl, Date timeout) {
-        def thread = new TestThread(this, lock, cl)
-        thread.start()
-        thread.completesBefore(timeout)
-    }
-
-    void onThreadStart(TestThread thread) {
-        lock.lock()
-        try {
-            threads << thread
-            threadsChanged.signalAll()
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    void onThreadComplete(TestThread thread, Throwable failure) {
-        lock.lock()
-        try {
-            threads.remove(thread)
-            if (failure) {
-                onFailure(failure)
-            }
-            threadsChanged.signalAll()
-        } finally {
-            lock.unlock()
-        }
-    }
-}
-
-class TestThread extends Thread {
-    private static final Logger LOG = LoggerFactory.getLogger(TestThread.class)
-    private final ConcurrentTestUtil owner
-    private final Runnable action
-    private final Lock lock
-    private final Condition stateChanged
-    private boolean complete
-
-    TestThread(ConcurrentTestUtil owner, Lock lock, Runnable action) {
-        this.owner = owner
-        this.action = action
-        this.lock = lock
-        this.stateChanged = lock.newCondition()
-    }
-
-    @Override
-    void start() {
-        LOG.info("$this started.")
-
-        lock.lock()
-        try {
-            owner.onThreadStart(this)
-            stateChanged.signalAll()
-        } finally {
-            lock.unlock()
-        }
-
-        super.start()
-    }
-
-    void running() {
-        lock.lock()
-        try {
-            if (complete) {
-                throw new IllegalStateException("$this should still be running, but is not.")
-            }
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    void completesBefore(Date timeout) {
-        lock.lock()
-        try {
-            LOG.info("Waiting for $this to complete.")
-            while (!complete) {
-                if (!stateChanged.awaitUntil(timeout)) {
-                    interrupt()
-                    throw new IllegalStateException("Timeout waiting for $this to complete.")
-                }
-            }
-            LOG.info("$this completed.")
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    @Override
-    void run() {
-        Throwable failure = null
-        try {
-            action.run()
-        } catch (Throwable t) {
-            failure = t
-        }
-
-        lock.lock()
-        try {
-            complete = true
-            stateChanged.signalAll()
-            owner.onThreadComplete(this, failure)
-            LOG.info("$this completed.")
-        } finally {
-            lock.unlock()
-        }
-    }
-}
-
-/**
- * Some potentially long running operation.
- */
-interface LongRunningAction {
-    /**
-     * Blocks until this action has completed. Asserts that the action completes in a 'short' time. Rethrows any exception from the action.
-     */
-    void completed()
-
-    /**
-     * Blocks until this action has completed. Asserts that the action completes within the specified time. Rethrows any exception from the action.
-     */
-    void completesWithin(long maxWaitValue, TimeUnit maxWaitUnits)
-
-    /**
-     * Blocks until this action has completed. Asserts that the action completes before the given time. Rethrows any exception from the action.
-     */
-    void completesBefore(Date timeout)
-}
-
-interface TestParticipant extends LongRunningAction {
-    /**
-     * Asserts that this test participant is running.
-     */
-    void running()
-}
-
-abstract class AbstractAction implements LongRunningAction {
-    
-    Date defaultExpiry
-
-    AbstractAction(Date defaultExpiry) {
-        this.defaultExpiry = defaultExpiry
-    }
-    
-    void completed() {
-        completesBefore(defaultExpiry)
-    }
-
-    void completesWithin(long maxWaitValue, TimeUnit maxWaitUnits) {
-        Date expiry = new Date(System.currentTimeMillis() + maxWaitUnits.toMillis(maxWaitValue))
-        completesBefore(expiry + 500)
-    }
-
-    abstract void completesBefore(Date timeout)
-}
-
-abstract class AbstractTestParticipant extends AbstractAction implements TestParticipant {
-    private final ConcurrentTestUtil owner
-
-    AbstractTestParticipant(ConcurrentTestUtil owner) {
-        super(owner.shortTimeout())
-        this.owner = owner
-    }
-}
-
-class TestParticipantImpl extends AbstractTestParticipant {
-    private final TestThread thread
-
-    TestParticipantImpl(ConcurrentTestUtil owner, TestThread thread) {
-        super(owner)
-        this.thread = thread
-    }
-
-    @Override
-    void completesBefore(Date timeout) {
-        thread.completesBefore(timeout)
-    }
-
-    void running() {
-        thread.running()
-    }
-}
-
-class CompositeTestParticipant extends AbstractTestParticipant {
-    private final List<TestParticipant> participants
-    private final Lock lock
-
-    CompositeTestParticipant(ConcurrentTestUtil owner, Lock lock, List<TestParticipant> participants) {
-        super(owner)
-        this.participants = participants
-        this.lock = lock
-    }
-
-    void running() {
-        lock.lock()
-        try {
-            participants*.running()
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    @Override
-    void completesBefore(Date timeout) {
-        lock.lock()
-        try {
-            participants*.completesBefore(timeout)
-        } finally {
-            lock.unlock()
-        }
-    }
-}
-
-class StoppableExecutorStub implements StoppableExecutor {
-    final ConcurrentTestUtil owner
-    final Set<TestThread> threads = new CopyOnWriteArraySet<TestThread>()
-
-    StoppableExecutorStub(ConcurrentTestUtil owner) {
-        this.owner = owner
-    }
-
-    void stop() {
-        def timeout = owner.shortTimeout()
-        threads.each { it.completesBefore(timeout) }
-    }
-
-    void stop(int timeoutValue, TimeUnit timeoutUnits) {
-        throw new UnsupportedOperationException()
-    }
-
-    void requestStop() {
-        throw new UnsupportedOperationException()
-    }
-
-    void execute(Runnable runnable) {
-        threads.add(owner.startThread(runnable))
-    }
-}
-
-class AbstractAsyncAction {
-    protected final ConcurrentTestUtil owner
-    private final Lock lock = new ReentrantLock()
-    protected final Condition condition = lock.newCondition()
-    protected Throwable failure
-
-    AbstractAsyncAction(ConcurrentTestUtil owner) {
-        this.owner = owner
-    }
-
-    protected Date shortTimeout() {
-        return owner.shortTimeout()
-    }
-
-    protected void onFailure(Throwable throwable) {
-        withLock {
-            failure = throwable
-            condition.signalAll()
-        }
-    }
-
-    protected def withLock(Closure cl) {
-        lock.lock()
-        try {
-            return cl.call()
-        } finally {
-            lock.unlock()
-        }
-    }
-}
-
-class StartAsyncAction extends AbstractAsyncAction {
-    private boolean started
-    private boolean completed
-    private Thread startThread
-
-    StartAsyncAction(ConcurrentTestUtil owner) {
-        super(owner)
-    }
-
-    /**
-     * Runs the given action, and then waits until another another thread calls {@link #done()}.  Asserts that the start action does not block waiting for
-     * the async action to complete.
-     *
-     * @param action The start action
-     * @return this
-     */
-    StartAsyncAction started(Runnable action) {
-        owner.onFailure this.&onFailure
-        doStart(action)
-        waitForStartToComplete()
-        waitForFinish()
-        return this
-    }
-
-    /**
-     * Marks that the async. action is now finished.
-     */
-    void done() {
-        waitForStartToComplete()
-        doFinish()
-    }
-
-
-    private void doStart(Runnable action) {
-        owner.startThread {
-            withLock {
-                if (startThread != null) {
-                    throw new IllegalStateException("Cannot start action multiple times.")
-                }
-                startThread = Thread.currentThread()
-                condition.signalAll()
-            }
-
-            action.run()
-
-            withLock {
-                started = true
-                condition.signalAll()
-            }
-        }
-
-        withLock {
-            while (startThread == null) {
-                condition.await()
-            }
-        }
-    }
-
-    private void doFinish() {
-        withLock {
-            if (completed) {
-                throw new IllegalStateException("Cannot run async action multiple times.")
-            }
-            completed = true
-            condition.signalAll()
-        }
-    }
-
-    private void waitForStartToComplete() {
-        Date timeout = shortTimeout()
-        withLock {
-            if (startThread == null) {
-                def e = new IllegalStateException("Action has not been started.")
-                e.printStackTrace()
-                throw e
-            }
-            if (Thread.currentThread() == startThread) {
-                def e = new IllegalStateException("Cannot wait for action to complete from the thread that is executing it.")
-                e.printStackTrace()
-                throw e
-            }
-            while (!started && !failure) {
-                if (!condition.awaitUntil(timeout)) {
-                    throw new IllegalStateException("Expected action to complete quickly, but it did not.")
-                }
-            }
-            if (failure) {
-                throw failure
-            }
-        }
-    }
-
-    private void waitForFinish() {
-        Date timeout = shortTimeout()
-        withLock {
-            while (!completed && !failure) {
-                if (!condition.awaitUntil(timeout)) {
-                    throw new IllegalStateException("Expected async action to complete, but it did not.")
-                }
-            }
-            if (failure) {
-                throw failure
-            }
-        }
-    }
-}
-
-abstract class AbstractWaitAction extends AbstractAsyncAction {
-    protected boolean started
-    protected boolean completed
-
-    AbstractWaitAction(ConcurrentTestUtil owner) {
-        super(owner)
-    }
-
-    protected void waitForBlockingActionToComplete() {
-        Date expiry = shortTimeout()
-        withLock {
-            while (!completed && !failure) {
-                if (!condition.awaitUntil(expiry)) {
-                    throw new IllegalStateException("Expected action to unblock, but it did not.")
-                }
-            }
-            if (failure) {
-                throw failure
-            }
-        }
-    }
-
-    protected void startBlockingAction(Runnable action) {
-        owner.startThread {
-            withLock {
-                started = true
-                condition.signalAll()
-            }
-
-            action.run()
-
-            withLock {
-                completed = true
-                condition.signalAll()
-            }
-        }
-
-        withLock {
-            while (!started) {
-                condition.await()
-            }
-        }
-    }
-
-    protected void assertBlocked() {
-        withLock {
-            if (completed) {
-                throw new IllegalStateException("Expected action to block, but it did not.")
-            }
-        }
-    }
-}
-
-class WaitForAsyncCallback extends AbstractWaitAction {
-    private boolean callbackCompleted
-    private Runnable callback
-
-    WaitForAsyncCallback(ConcurrentTestUtil owner) {
-        super(owner)
-    }
-
-    /**
-     * Runs the given action. Asserts that it blocks until after asynchronous callback is made. The action must register the callback using {@link #callbackLater(Runnable)}.
-     */
-    WaitForAsyncCallback start(Runnable action) {
-        owner.onFailure this.&onFailure
-
-        startBlockingAction(action)
-        waitForCallbackToBeRegistered()
-
-        Thread.sleep(500)
-
-        assertBlocked()
-        runCallbackAction()
-        waitForBlockingActionToComplete()
-
-        return this
-    }
-
-    /**
-     * Registers the callback which will unblock the action.
-     */
-    public void callbackLater(Runnable action) {
-        withLock {
-            if (callback) {
-                throw new IllegalStateException("Cannot register callback action multiple times.")
-            }
-            if (!started) {
-                throw new IllegalStateException("Action has not been started.")
-            }
-            callback = action
-            condition.signalAll()
-        }
-    }
-
-    private def runCallbackAction() {
-        owner.startThread {
-            callback.run()
-
-            withLock {
-                callbackCompleted = true
-                condition.signalAll()
-            }
-        }
-
-        Date timeout = shortTimeout()
-        withLock {
-            while (!callbackCompleted && !failure) {
-                if (!condition.awaitUntil(timeout)) {
-                    throw new IllegalStateException("Expected callback action to complete, but it did not.")
-                }
-            }
-            if (failure) {
-                throw failure
-            }
-        }
-    }
-
-    private void waitForCallbackToBeRegistered() {
-        Date expiry = shortTimeout()
-        withLock {
-            while (!callback && !failure && !completed) {
-                if (!condition.awaitUntil(expiry)) {
-                    throw new IllegalStateException("Expected action to register a callback action, but it did not.")
-                }
-            }
-            if (failure) {
-                throw failure
-            }
-            if (completed) {
-                throw new IllegalStateException("Expected action to block, but it did not.")
-            }
-        }
-    }
-}
-
-class WaitForAsyncAction extends AbstractWaitAction {
-    boolean asyncActionComplete
-
-    WaitForAsyncAction(ConcurrentTestUtil owner) {
-        super(owner)
-    }
-
-    WaitForAsyncAction start(Runnable action) {
-        owner.onFailure this.&onFailure
-        startBlockingAction(action)
-        waitForAsyncAction()
-        waitForBlockingActionToComplete()
-        return this
-    }
-
-    WaitForAsyncAction done() {
-        Thread.sleep(500)
-        assertBlocked()
-
-        withLock {
-            asyncActionComplete = true
-            condition.signalAll()
-        }
-
-        return this
-    }
-
-    def waitForAsyncAction() {
-        Date expiry = shortTimeout()
-        withLock {
-            while (!asyncActionComplete && !completed && !failure) {
-                if (!condition.awaitUntil(expiry)) {
-                    throw new IllegalStateException("Expected async action to be started, but it was not.")
-                }
-            }
-            if (failure) {
-                throw failure
-            }
-            if (!asyncActionComplete && completed) {
-                throw new IllegalStateException("Expected action to block, but it did not.")
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/ConcurrentSpecification.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/util/ConcurrentSpecification.groovy
index bf61c9c..5e334f5 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/util/ConcurrentSpecification.groovy
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/ConcurrentSpecification.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.util
 
-import org.gradle.tests.fixtures.ConcurrentTestUtil
+import org.gradle.test.fixtures.ConcurrentTestUtil
 import org.junit.Rule
 import spock.lang.Specification
 
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/HelperUtil.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/util/HelperUtil.groovy
index 2c1f9c3..91610da 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/util/HelperUtil.groovy
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/HelperUtil.groovy
@@ -15,57 +15,66 @@
  */
 package org.gradle.util
 
-import org.gradle.api.internal.AsmBackedClassGenerator
-import org.gradle.api.internal.project.taskfactory.ITaskFactory
-import org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory
-import org.gradle.api.internal.project.taskfactory.TaskFactory
-import org.gradle.api.Task
-import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.api.internal.project.DefaultProject
-import org.gradle.internal.reflect.DirectInstantiator
-import org.gradle.testfixtures.ProjectBuilder
+import org.apache.ivy.core.module.descriptor.Configuration
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor
 import org.apache.ivy.core.module.descriptor.DefaultExcludeRule
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
 import org.apache.ivy.core.module.id.ArtifactId
 import org.apache.ivy.core.module.id.ModuleId
-import org.apache.ivy.plugins.matcher.PatternMatcher
-import org.apache.ivy.plugins.matcher.ExactPatternMatcher
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor
 import org.apache.ivy.core.module.id.ModuleRevisionId
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
-import org.apache.ivy.core.module.descriptor.Configuration
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher
+import org.apache.ivy.plugins.matcher.PatternMatcher
+import org.codehaus.groovy.control.CompilerConfiguration
 import org.gradle.BuildResult
+import org.gradle.api.Task
 import org.gradle.api.artifacts.ModuleDependency
 import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
 import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
-import org.gradle.groovy.scripts.ScriptSource
-import org.codehaus.groovy.control.CompilerConfiguration
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.project.taskfactory.ITaskFactory
+import org.gradle.groovy.scripts.DefaultScript
 import org.gradle.groovy.scripts.Script
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.testfixtures.ProjectBuilder
+
 import java.rmi.server.UID
-import org.gradle.groovy.scripts.DefaultScript
 
 /**
  * @author Hans Dockter
  */
 class HelperUtil {
-
      public static final Closure TEST_CLOSURE = {}
-     private static final AsmBackedClassGenerator CLASS_GENERATOR = new AsmBackedClassGenerator()
-     private static final ITaskFactory TASK_FACTORY = new AnnotationProcessingTaskFactory(new TaskFactory(CLASS_GENERATOR))
 
      static <T extends Task> T createTask(Class<T> type) {
          return createTask(type, createRootProject())
      }
 
-     static <T extends Task> T createTask(Class<T> type, ProjectInternal project) {
+     static <T extends Task> T createTask(Class<T> type, Map taskFields) {
+         def task = createTask(type, createRootProject())
+         hackInTaskProperties(type, task, taskFields)
+         return task
+     }
+
+    private static void hackInTaskProperties(Class type, Task task, Map args) {
+        args.each { k, v ->
+            def field = type.getDeclaredField(k)
+            field.setAccessible(true)
+            field.set(task, v)
+        }
+    }
+
+    static <T extends Task> T createTask(Class<T> type, ProjectInternal project) {
          return createTask(type, project, 'name')
      }
 
      static <T extends Task> T createTask(Class<T> type, ProjectInternal project, String name) {
-         return TASK_FACTORY.createChild(project, new DirectInstantiator()).createTask([name: name, type: type])
+         return project.services.get(ITaskFactory).createTask([name: name, type: type])
      }
 
      static DefaultProject createRootProject() {
-         createRootProject(TemporaryFolder.newInstance().dir)
+         createRootProject(TestNameTestDirectoryProvider.newInstance().testDirectory)
      }
 
      static DefaultProject createRootProject(File rootDir) {
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/Matchers.java b/subprojects/core/src/testFixtures/groovy/org/gradle/util/Matchers.java
index ea4f30b..13e0cd3 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/util/Matchers.java
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/Matchers.java
@@ -36,6 +36,22 @@ import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.assertTrue;
 
 public class Matchers {
+    /**
+     * A reimplementation of hamcrest's isA() but without the broken generics.
+     */
+    @Factory
+    public static Matcher<Object> isA(final Class<?> type) {
+        return new BaseMatcher<Object>() {
+            public boolean matches(Object item) {
+                return type.isInstance(item);
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("instanceof ").appendValue(type);
+            }
+        };
+    }
+
     @Factory
     public static <T> Matcher<T> reflectionEquals(T equalsTo) {
         return new ReflectionEqualsMatcher<T>(equalsTo);
@@ -220,20 +236,6 @@ public class Matchers {
     }
 
     @Factory
-    public static Matcher<Object[]> isEmptyArray() {
-        return new BaseMatcher<Object[]>() {
-            public boolean matches(Object o) {
-                Object[] array = (Object[]) o;
-                return array != null && array.length == 0;
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("an empty array");
-            }
-        };
-    }
-
-    @Factory
     public static Matcher<Object> isSerializable() {
         return new BaseMatcher<Object>() {
             public boolean matches(Object o) {
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AbstractBinariesIntegrationSpec.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AbstractBinariesIntegrationSpec.groovy
index b8c5838..4df0c8d 100755
--- a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AbstractBinariesIntegrationSpec.groovy
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AbstractBinariesIntegrationSpec.groovy
@@ -18,7 +18,7 @@ package org.gradle.plugins.cpp
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.internal.os.OperatingSystem
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.runner.RunWith
 
 @RunWith(CppIntegrationTestRunner)
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/BinariesPlugin.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/BinariesPlugin.java
index 62c0378..07aac93 100644
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/BinariesPlugin.java
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/BinariesPlugin.java
@@ -27,15 +27,22 @@ import org.gradle.plugins.binaries.model.internal.DefaultCompilerRegistry;
 import org.gradle.plugins.binaries.model.internal.DefaultExecutable;
 import org.gradle.plugins.binaries.model.internal.DefaultLibrary;
 
+import javax.inject.Inject;
+
 /**
  * temp plugin, not sure what will provide the binaries container and model elements
  */
 public class BinariesPlugin implements Plugin<ProjectInternal> {
+    private final Instantiator instantiator;
+
+    @Inject
+    public BinariesPlugin(Instantiator instantiator) {
+        this.instantiator = instantiator;
+    }
 
     public void apply(final ProjectInternal project) {
         project.getPlugins().apply(BasePlugin.class);
 
-        Instantiator instantiator = project.getServices().get(Instantiator.class);
         project.getExtensions().create("compilers",
                 DefaultCompilerRegistry.class,
                 instantiator
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/CprojectDescriptor.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/CprojectDescriptor.groovy
index bd9dd51..860c149 100644
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/CprojectDescriptor.groovy
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/CprojectDescriptor.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.plugins.cpp.cdt.model
 
-import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
 
 /**
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptor.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptor.groovy
index 3b4f4f4..7f32bb4 100644
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptor.groovy
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptor.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.plugins.cpp.cdt.model
 
-import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
 
 /**
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/tasks/GenerateMetadataFileTask.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/tasks/GenerateMetadataFileTask.groovy
index b34fa54..8f25b5e 100644
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/tasks/GenerateMetadataFileTask.groovy
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/tasks/GenerateMetadataFileTask.groovy
@@ -15,6 +15,7 @@
  */
 package org.gradle.plugins.cpp.cdt.tasks
 
+import org.gradle.api.internal.ClosureBackedAction
 import org.gradle.internal.Factory
 import org.gradle.listener.ActionBroadcast
 import org.gradle.plugins.cpp.cdt.model.CprojectSettings
@@ -45,6 +46,6 @@ class GenerateMetadataFileTask<T extends PersistableConfigurationObject> extends
     }
 
     void onConfigure(Closure configure) {
-        configures.add(configure)
+        configures.add(new ClosureBackedAction(configure))
     }
 }
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompiler.java
index 37a31e9..b6e4738 100755
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompiler.java
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompiler.java
@@ -24,6 +24,7 @@ import org.gradle.api.tasks.WorkResult;
 import org.gradle.internal.Factory;
 import org.gradle.plugins.cpp.internal.CppCompileSpec;
 import org.gradle.process.internal.ExecAction;
+import org.gradle.util.GFileUtils;
 
 import java.io.File;
 
@@ -60,7 +61,7 @@ public class CommandLineCppCompiler<T extends CppCompileSpec> implements CppComp
 
     private void ensureDirsExist(File... dirs) {
         for (File dir : dirs) {
-            dir.mkdirs();
+            GFileUtils.mkdirs(dir);
         }
     }
 
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompileSpec.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompileSpec.groovy
index ccb132d..65efb5b 100755
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompileSpec.groovy
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompileSpec.groovy
@@ -170,7 +170,9 @@ class GppCompileSpec implements CompileSpec, StandardCppCompiler, CompileTaskAwa
     }
 
     void includes(Iterable<File> includeRoots) {
-        includeRoots.each { task.inputs.dir(it) }
+        for (File includeRoot in includeRoots) {
+            task.inputs.dir(includeRoot)
+        }
         includes.from(includeRoots)
     }
 
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompilerPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompilerPlugin.groovy
index cdad9ae..433c2d4 100644
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompilerPlugin.groovy
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompilerPlugin.groovy
@@ -16,7 +16,8 @@
 package org.gradle.plugins.cpp.gpp
 
 import org.gradle.api.Plugin
-import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.Project
+import org.gradle.api.internal.file.FileResolver
 import org.gradle.internal.Factory
 import org.gradle.internal.os.OperatingSystem
 import org.gradle.plugins.binaries.BinariesPlugin
@@ -25,18 +26,26 @@ import org.gradle.plugins.cpp.gpp.internal.GppCompilerAdapter
 import org.gradle.process.internal.DefaultExecAction
 import org.gradle.process.internal.ExecAction
 
+import javax.inject.Inject
+
 /**
  * A {@link Plugin} which makes the <a href="http://gcc.gnu.org/">GNU G++ compiler</a> available for compiling C/C++ code.
  */
-class GppCompilerPlugin implements Plugin<ProjectInternal> {
+class GppCompilerPlugin implements Plugin<Project> {
+    private final FileResolver fileResolver
+
+    @Inject
+    GppCompilerPlugin(FileResolver fileResolver) {
+        this.fileResolver = fileResolver
+    }
 
-    void apply(ProjectInternal project) {
+    void apply(Project project) {
         project.plugins.apply(BinariesPlugin)
         project.extensions.getByType(CompilerRegistry).add(new GppCompilerAdapter(
                 OperatingSystem.current(),
                 new Factory<ExecAction>() {
                     ExecAction create() {
-                        new DefaultExecAction(project.getFileResolver())
+                        new DefaultExecAction(fileResolver)
                     }
                 }))
     }
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/MicrosoftVisualCppPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/MicrosoftVisualCppPlugin.groovy
index 7c49d10..a64ea77 100755
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/MicrosoftVisualCppPlugin.groovy
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/MicrosoftVisualCppPlugin.groovy
@@ -19,7 +19,8 @@
 package org.gradle.plugins.cpp.msvcpp
 
 import org.gradle.api.Plugin
-import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.Project
+import org.gradle.api.internal.file.FileResolver
 import org.gradle.internal.Factory
 import org.gradle.plugins.binaries.BinariesPlugin
 import org.gradle.plugins.binaries.model.CompilerRegistry
@@ -28,11 +29,20 @@ import org.gradle.process.internal.ExecAction
 import org.gradle.process.internal.DefaultExecAction
 import org.gradle.internal.os.OperatingSystem
 
+import javax.inject.Inject
+
 /**
  * A {@link Plugin} which makes the Microsoft Visual C++ compiler available to compile C/C++ code.
  */
-class MicrosoftVisualCppPlugin implements Plugin<ProjectInternal> {
-    void apply(ProjectInternal project) {
+class MicrosoftVisualCppPlugin implements Plugin<Project> {
+    private final FileResolver fileResolver;
+
+    @Inject
+    MicrosoftVisualCppPlugin(FileResolver fileResolver) {
+        this.fileResolver = fileResolver
+    }
+
+    void apply(Project project) {
         project.plugins.apply(BinariesPlugin)
 
         if (!OperatingSystem.current().windows) {
@@ -43,7 +53,7 @@ class MicrosoftVisualCppPlugin implements Plugin<ProjectInternal> {
                 OperatingSystem.current(),
                 new Factory<ExecAction>() {
                     ExecAction create() {
-                        new DefaultExecAction(project.fileResolver)
+                        new DefaultExecAction(fileResolver)
                     }
                 }
         ))
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptorSpec.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptorSpec.groovy
index 4880a5c..7341abc 100644
--- a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptorSpec.groovy
+++ b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptorSpec.groovy
@@ -15,13 +15,13 @@
  */
 package org.gradle.plugins.cpp.cdt.model
 
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 class ProjectDescriptorSpec extends Specification {
 
-    @Rule public TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     ProjectDescriptor descriptor = new ProjectDescriptor()
 
     def "method"() {
diff --git a/subprojects/diagnostics/diagnostics.gradle b/subprojects/diagnostics/diagnostics.gradle
index c430922..cbbf7b1 100644
--- a/subprojects/diagnostics/diagnostics.gradle
+++ b/subprojects/diagnostics/diagnostics.gradle
@@ -18,7 +18,7 @@ dependencies {
     groovy libraries.groovy
     compile project(':core')
     compile project(':plugins')
-    testCompile project(':coreImpl')
+    compile project(':coreImpl')
 }
 
 useTestFixtures()
diff --git a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTaskIntegrationTest.groovy b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTaskIntegrationTest.groovy
index 03d02e6..2a0e043 100644
--- a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTaskIntegrationTest.groovy
+++ b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTaskIntegrationTest.groovy
@@ -22,7 +22,7 @@ import static org.gradle.util.TextUtil.toPlatformLineSeparators
 
 class DependencyInsightReportTaskIntegrationTest extends AbstractIntegrationSpec {
     def setup() {
-        distribution.requireOwnUserHomeDir()
+        executer.requireOwnGradleUserHomeDir()
     }
 
     def "shows basic single tree with repeated dependency"() {
@@ -56,10 +56,10 @@ class DependencyInsightReportTaskIntegrationTest extends AbstractIntegrationSpec
         then:
         output.contains(toPlatformLineSeparators("""
 org:leaf2:1.0
-+--- org:top:1.0
-|    \\--- conf
-\\--- org:middle:1.0
-     \\--- org:top:1.0 (*)
++--- org:middle:1.0
+|    \\--- org:top:1.0
+|         \\--- conf
+\\--- org:top:1.0 (*)
 
 (*) - dependencies omitted (listed previously)
 """))
@@ -161,6 +161,104 @@ org:leaf:2.0 -> 1.0
 """))
     }
 
+    def "shows multiple outgoing dependencies"() {
+        given:
+        mavenRepo.module("org", "leaf", "1.0").publish()
+        mavenRepo.module("org", "middle", "1.0")
+                .dependsOn("org", "leaf", "1.0")
+                .dependsOn("org", "leaf", "[1.0,2.0]")
+                .dependsOn("org", "leaf", "latest.integration")
+                .publish()
+        mavenRepo.module("org", "top", "1.0")
+                .dependsOn("org", "middle", "1.0")
+                .dependsOn("org", "middle", "[1.0,2.0]")
+                .dependsOn("org", "middle", "latest.integration")
+                .publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:top:1.0'
+                conf 'org:top:[1.0,2.0]'
+                conf 'org:top:latest.integration'
+            }
+            task insight(type: DependencyInsightReportTask) {
+                setDependencySpec { it.requested.name == 'leaf' }
+                configuration = configurations.conf
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        // TODO - need to use a fixed ordering for dynamic requested versions
+        output.contains(toPlatformLineSeparators("""
+org:leaf:1.0
+\\--- org:middle:1.0
+     \\--- org:top:1.0
+          \\--- conf
+"""))
+        output.contains(toPlatformLineSeparators("""
+org:leaf:latest.integration -> 1.0
+\\--- org:middle:1.0
+     \\--- org:top:1.0
+          \\--- conf
+"""))
+        output.contains(toPlatformLineSeparators("""
+org:leaf:[1.0,2.0] -> 1.0
+\\--- org:middle:1.0
+     \\--- org:top:1.0
+          \\--- conf
+"""))
+    }
+
+    def "shows substituted versions"() {
+        given:
+        mavenRepo.module("org", "leaf", 1.0).publish()
+        mavenRepo.module("org", "leaf", 2.0).publish()
+
+        mavenRepo.module("org", "foo", 1.0).dependsOn('org', 'leaf', '1.0').publish()
+        mavenRepo.module("org", "bar", 1.0).dependsOn('org', 'leaf', '2.0').publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf {
+                    resolutionStrategy.eachDependency { if (it.requested.name == 'leaf') { it.useVersion('1.0') } }
+                }
+            }
+            dependencies {
+                conf 'org:foo:1.0', 'org:bar:1.0'
+            }
+            task insight(type: DependencyInsightReportTask) {
+                configuration = configurations.conf
+                setDependencySpec { it.requested.name == 'leaf' }
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+org:leaf:1.0 (selected by rule)
+\\--- org:foo:1.0
+     \\--- conf
+
+org:leaf:2.0 -> 1.0
+\\--- org:bar:1.0
+     \\--- conf
+"""))
+    }
+
     def "forced version matches the conflict resolution"() {
         given:
         mavenRepo.module("org", "leaf", 1.0).publish()
@@ -318,7 +416,7 @@ org:leaf:2.0 -> 1.0
         run "insight"
 
         then:
-        output.contains("No resolved dependencies matching given input were found")
+        output.contains("No dependencies matching given input were found")
     }
 
     def "informs that nothing matches the input dependency"() {
@@ -345,10 +443,10 @@ org:leaf:2.0 -> 1.0
         run "insight"
 
         then:
-        output.contains("No resolved dependencies matching given input were found")
+        output.contains("No dependencies matching given input were found")
     }
 
-    def "deals with unresolved dependencies"() {
+    def "marks modules that can't be resolved as 'FAILED'"() {
         given:
         mavenRepo.module("org", "top").dependsOn("middle").publish()
 
@@ -372,7 +470,168 @@ org:leaf:2.0 -> 1.0
         run "insight"
 
         then:
-        output.contains("No resolved dependencies matching given input were found")
+        output.contains(toPlatformLineSeparators("""
+org:middle:1.0 FAILED
+\\--- org:top:1.0
+     \\--- conf
+"""))
+    }
+
+    def "marks modules that can't be resolved after forcing a different version as 'FAILED'"() {
+        given:
+        mavenRepo.module("org", "top").dependsOn("org", "middle", "1.0").publish()
+        mavenRepo.module("org", "middle", 1.0).publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf {
+                    resolutionStrategy {
+                        force "org:middle:2.0"
+                    }
+                }
+            }
+            dependencies {
+                conf 'org:top:1.0'
+            }
+            task insight(type: DependencyInsightReportTask) {
+                setDependencySpec { it.requested.name == 'middle' }
+                configuration = configurations.conf
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+org:middle:2.0 (forced) FAILED
+
+org:middle:1.0 -> 2.0 FAILED
+\\--- org:top:1.0
+     \\--- conf
+"""))
+    }
+
+    def "marks modules that can't be resolved after conflict resolution as 'FAILED'"() {
+        given:
+        mavenRepo.module("org", "top").dependsOn("org", "middle", "1.0").publish()
+        mavenRepo.module("org", "middle", 1.0).publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:top:1.0'
+                conf 'org:middle:2.0'
+            }
+            task insight(type: DependencyInsightReportTask) {
+                setDependencySpec { it.requested.name == 'middle' }
+                configuration = configurations.conf
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+org:middle:2.0 (conflict resolution) FAILED
+\\--- conf
+
+org:middle:1.0 -> 2.0 FAILED
+\\--- org:top:1.0
+     \\--- conf
+"""))
+    }
+
+    def "marks modules that can't be resolved after substitution as 'FAILED'"() {
+        given:
+        mavenRepo.module("org", "top").dependsOn("org", "middle", "1.0").publish()
+        mavenRepo.module("org", "middle", "1.0").publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf {
+                    resolutionStrategy.eachDependency { if (it.requested.name == 'middle') { it.useVersion('2.0+') } }
+                }
+            }
+            dependencies {
+                conf 'org:top:1.0'
+            }
+            task insight(type: DependencyInsightReportTask) {
+                setDependencySpec { it.requested.name == 'middle' }
+                configuration = configurations.conf
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+org:middle:2.0+ (selected by rule) FAILED
+
+org:middle:1.0 -> 2.0+ FAILED
+\\--- org:top:1.0
+     \\--- conf
+"""))
+    }
+
+    def "shows multiple failed outgoing dependencies"() {
+        given:
+        mavenRepo.module("org", "leaf", "1.0")
+        mavenRepo.module("org", "top", "1.0")
+                .dependsOn("org", "leaf", "1.0")
+                .dependsOn("org", "leaf", "[1.5,2.0]")
+                .dependsOn("org", "leaf", "1.6+")
+                .publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:top:1.0'
+            }
+            task insight(type: DependencyInsightReportTask) {
+                setDependencySpec { it.requested.name == 'leaf' }
+                configuration = configurations.conf
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        // TODO - need to use a fixed ordering for dynamic requested versions
+        output.contains(toPlatformLineSeparators("""
+org:leaf:1.0 FAILED
+\\--- org:top:1.0
+     \\--- conf
+"""))
+        output.contains(toPlatformLineSeparators("""
+org:leaf:1.6+ FAILED
+\\--- org:top:1.0
+     \\--- conf
+"""))
+        output.contains(toPlatformLineSeparators("""
+org:leaf:[1.5,2.0] FAILED
+\\--- org:top:1.0
+     \\--- conf
+"""))
     }
 
     def "deals with dependency cycles"() {
diff --git a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskIntegrationTest.groovy b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskIntegrationTest.groovy
index a24ec66..ae95f5f 100644
--- a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskIntegrationTest.groovy
+++ b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskIntegrationTest.groovy
@@ -21,7 +21,7 @@ import static org.gradle.util.TextUtil.toPlatformLineSeparators
 
 class DependencyReportTaskIntegrationTest extends AbstractIntegrationSpec {
     def setup() {
-        distribution.requireOwnUserHomeDir()
+        executer.requireOwnGradleUserHomeDir()
     }
 
     def "omits repeated dependencies in case of circular dependencies"() {
@@ -58,10 +58,10 @@ class DependencyReportTaskIntegrationTest extends AbstractIntegrationSpec {
         output.contains "(*) - dependencies omitted (listed previously)"
     }
 
-    def "renders even if resolution fails"() {
+    def "marks modules that can't be resolved as 'FAILED'"() {
         given:
-        mavenRepo.module("foo", "bar", 1.0).dependsOn("i dont exist").publish()
-        mavenRepo.module("foo", "baz", 1.0).dependsOn("foo:bar:1.0").publish()
+        mavenRepo.module("foo", "bar", 1.0).dependsOn("unknown").publish()
+        mavenRepo.module("foo", "baz", 1.0).dependsOn("bar").publish()
 
         file("build.gradle") << """
             repositories {
@@ -76,13 +76,116 @@ class DependencyReportTaskIntegrationTest extends AbstractIntegrationSpec {
 
         when:
         executer.allowExtraLogging = false
-        runAndFail "dependencies"
+        run "dependencies"
 
         then:
-        errorOutput.contains('Could not resolve all dependencies')
         output.contains(toPlatformLineSeparators("""
 foo
++--- i:dont:exist FAILED
 \\--- foo:baz:1.0
+     \\--- foo:bar:1.0
+          \\--- foo:unknown:1.0 FAILED
+"""
+        ))
+    }
+
+    def "marks dynamic versions that can't be resolved as 'FAILED'"() {
+        given:
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations { foo }
+            dependencies {
+                foo 'org:unknown:1.2+'
+                foo 'org:unknown:[1.0,2.0]'
+                foo 'org:unknown:latest.integration'
+                foo 'org:unknown:latest.release'
+                foo 'org:other:1.2'
+                foo 'org:other:2.0+'
+            }
+        """
+
+        when:
+        executer.allowExtraLogging = false
+        run "dependencies"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+foo
++--- org:unknown:1.2+ FAILED
++--- org:unknown:[1.0,2.0] FAILED
++--- org:unknown:latest.integration FAILED
++--- org:unknown:latest.release FAILED
++--- org:other:1.2 FAILED
+\\--- org:other:2.0+ FAILED
+"""
+        ))
+    }
+
+    def "marks modules that can't be resolved after conflict resolution as 'FAILED'"() {
+        given:
+        mavenRepo.module("foo", "bar", 1.0).dependsOn("foo", "baz", "2.0").publish()
+        mavenRepo.module("foo", "baz", 1.0).publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations { config }
+            dependencies {
+              config 'foo:bar:1.0'
+              config 'foo:baz:1.0'
+            }
+        """
+
+        when:
+        executer.allowExtraLogging = false
+        run "dependencies"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+config
++--- foo:bar:1.0
+|    \\--- foo:baz:2.0 FAILED
+\\--- foo:baz:1.0 -> 2.0 FAILED
+"""
+        ))
+    }
+
+    def "marks modules that can't be resolved after forcing a different version as 'FAILED'"() {
+        given:
+        mavenRepo.module("org", "libA", 1.0).dependsOn("org", "libB", "1.0").dependsOn("org", "libC", "1.0").publish()
+        mavenRepo.module("org", "libB", 1.0).publish()
+        mavenRepo.module("org", "libC", 1.0).publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+              config {
+                resolutionStrategy {
+                  force('org:libB:2.0')
+                  force('org:libC:1.2+')
+                }
+              }
+            }
+            dependencies {
+              config 'org:libA:1.0'
+            }
+        """
+
+        when:
+        executer.allowExtraLogging = false
+        run "dependencies"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+config
+\\--- org:libA:1.0
+     +--- org:libB:1.0 -> 2.0 FAILED
+     \\--- org:libC:1.0 -> 1.2+ FAILED
 """
         ))
     }
@@ -266,8 +369,8 @@ rootProject.name = 'root'
 +--- bar:bar:5.0 -> 6.0
 |    \\--- foo:foo:3.0
 +--- bar:bar:6.0 (*)
-+--- foo:foo:1.0 -> 3.0 (*)
-\\--- foo:foo:2.0 -> 3.0 (*)
++--- foo:foo:1.0 -> 3.0
+\\--- foo:foo:2.0 -> 3.0
 """))
     }
 
@@ -375,10 +478,9 @@ rootProject.name = 'root'
 |    \\--- org:middle2:1.0
 |         +--- org:leaf3:1.0
 |         \\--- org:leaf4:1.0 -> 2.0
-\\--- org:leaf4:2.0 (*)
+\\--- org:leaf4:2.0
 """))
     }
-
     def "tells if there are no dependencies"() {
         given:
         buildFile << "configurations { foo }"
@@ -417,4 +519,74 @@ rootProject.name = 'root'
         noExceptionThrown()
         //note that 'a' project dependencies are not being resolved
     }
+
+    def "report can be limited to a single configuration via command-line parameter"() {
+        given:
+        mavenRepo.module("org", "leaf1").publish()
+        mavenRepo.module("org", "leaf2").publish()
+        mavenRepo.module("org", "leaf3").publish()
+        mavenRepo.module("org", "leaf4").publish()
+
+        mavenRepo.module("org", "toplevel1").dependsOn('leaf1', 'leaf2').publish()
+        mavenRepo.module("org", "toplevel2").dependsOn('leaf3', 'leaf4').publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+
+            configurations {
+                conf1
+                conf2
+            }
+
+            dependencies {
+                conf1 'org:toplevel1:1.0'
+                conf2 'org:toplevel2:1.0'
+            }
+        """
+
+        when:
+        run "dependencies", "--configuration", "conf2"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+conf2
+\\--- org:toplevel2:1.0
+     +--- org:leaf3:1.0
+     \\--- org:leaf4:1.0
+"""))
+
+        !output.contains("conf1")
+    }
+
+    void "marks module that cannot be resolved due to broken dependency rule as 'FAILED'"() {
+        mavenRepo.module("org.utils", "impl", '1.3').publish()
+
+        buildFile << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+
+            configurations { conf }
+
+            dependencies {
+                conf 'org.utils:impl:1.3'
+            }
+
+            configurations.conf.resolutionStrategy {
+                eachDependency {
+                    throw new RuntimeException("Ka-booom!")
+	            }
+	        }
+"""
+
+        when:
+        run "dependencies"
+
+        then:
+        output.contains(toPlatformLineSeparators("""conf
+\\--- org.utils:impl:1.3 FAILED
+"""))
+    }
 }
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/ResolutionResultApiIntegrationTest.groovy b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/ResolutionResultApiIntegrationTest.groovy
new file mode 100644
index 0000000..1aaf46d
--- /dev/null
+++ b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/ResolutionResultApiIntegrationTest.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.api.tasks.diagnostics
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+class ResolutionResultApiIntegrationTest extends AbstractIntegrationSpec {
+
+    def setup() {
+        executer.requireOwnGradleUserHomeDir()
+    }
+
+    /*
+    The ResolutionResult API is also covered by the dependency report integration tests.
+     */
+
+    def "selection reasons are described"() {
+        given:
+        mavenRepo.module("org", "leaf", 1.0).publish()
+        mavenRepo.module("org", "leaf", 2.0).publish()
+        mavenRepo.module("org", "foo", 0.5).publish()
+
+        mavenRepo.module("org", "foo", 1.0).dependsOn('org', 'leaf', '1.0').publish()
+        mavenRepo.module("org", "bar", 1.0).dependsOn('org', 'leaf', '2.0').publish()
+        mavenRepo.module("org", "baz", 1.0).dependsOn('org', 'foo',  '1.0').publish()
+
+        file("settings.gradle") << "rootProject.name = 'cool-project'"
+
+        file("build.gradle") << """
+            version = '5.0'
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            configurations.conf.resolutionStrategy.force 'org:leaf:2.0'
+            dependencies {
+                conf 'org:foo:0.5', 'org:bar:1.0', 'org:baz:1.0'
+            }
+            task resolutionResult << {
+                def result = configurations.conf.incoming.resolutionResult
+                result.allModuleVersions {
+                    println it.id.name + ":" + it.id.version + " " + it.selectionReason.description
+                }
+            }
+        """
+
+        when:
+        run "resolutionResult"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+cool-project:5.0 root
+foo:1.0 conflict resolution
+leaf:2.0 forced
+bar:1.0 requested
+baz:1.0 requested
+"""))
+    }
+}
diff --git a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskIntegrationTest.groovy b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskIntegrationTest.groovy
new file mode 100644
index 0000000..b262c65
--- /dev/null
+++ b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskIntegrationTest.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks.diagnostics
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import spock.lang.Issue
+
+class TaskReportTaskIntegrationTest extends AbstractIntegrationSpec {
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2023")
+    def "can deal with tasks with named task dependencies that are created by rules"() {
+        when:
+        buildFile << getBuildScriptContent()
+
+        then:
+        succeeds "tasks", "--all"
+    }
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2023")
+    def "can deal with tasks with named task dependencies that are created by rules - multiproject"() {
+        when:
+        settingsFile << "include 'module'"
+
+        file("module/build.gradle") << getBuildScriptContent()
+
+        then:
+        succeeds "tasks", "--all"
+    }
+
+    protected static String getBuildScriptContent() {
+        """
+            tasks.addRule("test rule") {
+                if (it.startsWith("autoCreate")) {
+                    def name = it - "autoCreate"
+                    name = name[0].toLowerCase() + name[1..-1]
+                    if (tasks.findByName(name)) {
+                        project.tasks.add(it)
+                    }
+                }
+            }
+
+            // Source task must be alphabetically before task that is created by dependency
+            task aaa { dependsOn("autoCreateFoo") }
+            task foo
+        """
+    }
+
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTask.groovy b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTask.groovy
index 4df41c3..30657fa 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTask.groovy
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTask.groovy
@@ -20,10 +20,10 @@ package org.gradle.api.tasks.diagnostics;
 import org.gradle.api.Action
 import org.gradle.api.DefaultTask
 import org.gradle.api.Incubating
+import org.gradle.api.InvalidUserDataException
 import org.gradle.api.artifacts.Configuration
 import org.gradle.api.artifacts.result.DependencyResult
 import org.gradle.api.artifacts.result.ResolutionResult
-import org.gradle.api.artifacts.result.ResolvedDependencyResult
 import org.gradle.api.internal.tasks.CommandLineOption
 import org.gradle.api.specs.Spec
 import org.gradle.api.tasks.TaskAction
@@ -39,6 +39,9 @@ import org.gradle.logging.StyledTextOutputFactory
 import javax.inject.Inject
 
 import static org.gradle.logging.StyledTextOutput.Style.Info
+import static org.gradle.logging.StyledTextOutput.Style.Failure
+import static org.gradle.logging.StyledTextOutput.Style.Identifier
+import static org.gradle.logging.StyledTextOutput.Style.Description
 
 /**
  * Generates a report that attempts to answer questions like:
@@ -143,11 +146,11 @@ public class DependencyInsightReportTask extends DefaultTask {
     @TaskAction
     public void report() {
         if (configuration == null) {
-            throw new ReportException("Dependency insight report cannot be generated because the input configuration was not specified. "
+            throw new InvalidUserDataException("Dependency insight report cannot be generated because the input configuration was not specified. "
                     + "\nIt can be specified from the command line, e.g: '$path --configuration someConf --dependency someDep'")
         }
         if (dependencySpec == null) {
-            throw new ReportException("Dependency insight report cannot be generated because the dependency to show was not specified."
+            throw new InvalidUserDataException("Dependency insight report cannot be generated because the dependency to show was not specified."
                     + "\nIt can be specified from the command line, e.g: '$path --dependency someDep'")
         }
 
@@ -155,37 +158,40 @@ public class DependencyInsightReportTask extends DefaultTask {
 
         Set<DependencyResult> selectedDependencies = new LinkedHashSet<DependencyResult>()
         result.allDependencies { DependencyResult it ->
-            //TODO SF revisit when developing unresolved dependencies story
-            if (it instanceof ResolvedDependencyResult && dependencySpec.isSatisfiedBy(it)) {
+            if (dependencySpec.isSatisfiedBy(it)) {
                 selectedDependencies << it
             }
         }
 
         if (selectedDependencies.empty) {
-            output.println("No resolved dependencies matching given input were found in $configuration")
+            output.println("No dependencies matching given input were found in $configuration")
             return
         }
 
         def sortedDeps = new DependencyInsightReporter().prepare(selectedDependencies)
 
         def nodeRenderer = new NodeRenderer() {
-            void renderNode(StyledTextOutput output, RenderableDependency node, Set<RenderableDependency> children, boolean alreadyRendered) {
-                boolean leaf = children.empty
-                output.text(leaf? DependencyInsightReportTask.this.configuration.name : node.name);
+            void renderNode(StyledTextOutput output, RenderableDependency node, boolean alreadyRendered) {
+                boolean leaf = node.children.empty
+                output.text(leaf ? DependencyInsightReportTask.this.configuration.name : node.name);
                 if (alreadyRendered && !leaf) {
                     output.withStyle(Info).text(" (*)")
                 }
             }
         }
+
         def dependencyGraphRenderer = new DependencyGraphRenderer(renderer, nodeRenderer)
 
         int i = 1
         for (RenderableDependency dependency: sortedDeps) {
             renderer.visit(new Action<StyledTextOutput>() {
                 public void execute(StyledTextOutput out) {
-                    out.withStyle(StyledTextOutput.Style.Identifier).text(dependency.name);
+                    out.withStyle(Identifier).text(dependency.name);
                     if (dependency.description) {
-                        out.withStyle(StyledTextOutput.Style.Description).text(" (" + dependency.description + ")")
+                        out.withStyle(Description).text(" ($dependency.description)")
+                    }
+                    if (!dependency.resolvable) {
+                        out.withStyle(Failure).text(" FAILED")
                     }
                 }
             }, true);
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTask.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTask.java
index 16d682f..2a03f4c 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTask.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTask.java
@@ -17,20 +17,17 @@ package org.gradle.api.tasks.diagnostics;
 
 import org.gradle.api.Project;
 import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.internal.tasks.CommandLineOption;
 import org.gradle.api.tasks.diagnostics.internal.DependencyReportRenderer;
 import org.gradle.api.tasks.diagnostics.internal.ReportRenderer;
 import org.gradle.api.tasks.diagnostics.internal.dependencies.AsciiDependencyReportRenderer;
 
 import java.io.IOException;
-import java.util.Comparator;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
+import java.util.*;
 
 /**
- * Displays the dependency tree for a project. Can be configured to output to a file, and to optionally output a
- * graphviz compatible "dot" graph. An instance of this type is used when you execute the {@code dependencies} task from
- * the command-line.
+ * Displays the dependency tree for a project. An instance of this type is used when you
+ * execute the {@code dependencies} task from the command-line.
  *
  * @author Phil Messenger
  */
@@ -70,7 +67,7 @@ public class DependencyReportTask extends AbstractReportTask {
     }
 
     /**
-     * Returns the configurations to generate the report for. Default to all configurations of this task's containing
+     * Returns the configurations to generate the report for. Defaults to all configurations of this task's containing
      * project.
      *
      * @return the configurations.
@@ -87,4 +84,14 @@ public class DependencyReportTask extends AbstractReportTask {
     public void setConfigurations(Set<Configuration> configurations) {
         this.configurations = configurations;
     }
+
+    /**
+     * Sets the single configuration (by name) to generate the report for.
+     *
+     * @param configurationName name of the configuration to generate the report for
+     */
+    @CommandLineOption(options = "configuration", description = "The configuration to generate the report for.")
+    public void setConfiguration(String configurationName) {
+        this.configurations = Collections.singleton(getProject().getConfigurations().getByName(configurationName));
+    }
 }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/ReportException.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/ReportException.java
deleted file mode 100644
index 4539b34..0000000
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/ReportException.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.tasks.diagnostics;
-
-import org.gradle.api.GradleException;
-
-/**
- * by Szczepan Faber, created at: 9/20/12
- */
-public class ReportException extends GradleException {
-
-    public ReportException(String message) {
-        super(message);
-    }
-}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java
index 8fa2c2c..e93040e 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java
@@ -20,6 +20,7 @@ import org.gradle.api.Project;
 import org.gradle.api.Rule;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.CommandLineOption;
+import org.gradle.api.internal.tasks.TaskContainerInternal;
 import org.gradle.api.tasks.diagnostics.internal.*;
 
 import java.io.IOException;
@@ -59,12 +60,17 @@ public class TaskReportTask extends AbstractReportTask {
 
         SingleProjectTaskReportModel projectTaskModel = new SingleProjectTaskReportModel(taskDetailsFactory);
         ProjectInternal projectInternal = (ProjectInternal) project;
-        projectTaskModel.build(Sets.union(projectInternal.getTasks(), projectInternal.getImplicitTasks()));
+        TaskContainerInternal tasks = projectInternal.getTasks();
+        tasks.actualize();
+        projectTaskModel.build(Sets.union(tasks, projectInternal.getImplicitTasks()));
         aggregateModel.add(projectTaskModel);
 
-        for (Project subprojects : project.getSubprojects()) {
+        for (Project subproject : project.getSubprojects()) {
             SingleProjectTaskReportModel subprojectTaskModel = new SingleProjectTaskReportModel(taskDetailsFactory);
-            subprojectTaskModel.build(subprojects.getTasks());
+            ProjectInternal subprojectInternal = (ProjectInternal) subproject;
+            TaskContainerInternal subprojectTasks = subprojectInternal.getTasks();
+            subprojectTasks.actualize();
+            subprojectTaskModel.build(subprojectTasks);
             aggregateModel.add(subprojectTaskModel);
         }
 
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/DependencyReportRenderer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/DependencyReportRenderer.java
index 2c44eb2..d26be81 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/DependencyReportRenderer.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/DependencyReportRenderer.java
@@ -20,7 +20,7 @@ import org.gradle.api.artifacts.Configuration;
 import java.io.IOException;
 
 /**
- * A {@code DependencyReportRenderer} is responsible for rendering the model of a project dependency report.
+ * Renders the model of a project dependency report.
  *
  * @author Phil Messenger
  */
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/ReportRenderer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/ReportRenderer.java
index 084f92c..414269c 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/ReportRenderer.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/ReportRenderer.java
@@ -22,7 +22,7 @@ import java.io.File;
 import java.io.IOException;
 
 /**
- * <p>A {@code ProjectReportRenderer} is responsible for rendering the model of a project report.</p>
+ * Renders the model of a project report.
  */
 public interface ReportRenderer {
     /**
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/dependencies/AsciiDependencyReportRenderer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/dependencies/AsciiDependencyReportRenderer.java
index 61c33c9..11a44e9 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/dependencies/AsciiDependencyReportRenderer.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/dependencies/AsciiDependencyReportRenderer.java
@@ -18,7 +18,6 @@ package org.gradle.api.tasks.diagnostics.internal.dependencies;
 import org.gradle.api.Action;
 import org.gradle.api.Project;
 import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.ResolvedConfiguration;
 import org.gradle.api.artifacts.result.ResolutionResult;
 import org.gradle.api.tasks.diagnostics.internal.DependencyReportRenderer;
 import org.gradle.api.tasks.diagnostics.internal.GraphRenderer;
@@ -82,13 +81,9 @@ public class AsciiDependencyReportRenderer extends TextReportRenderer implements
     public void completeConfiguration(Configuration configuration) {}
 
     public void render(Configuration configuration) throws IOException {
-        ResolvedConfiguration resolvedConfiguration = configuration.getResolvedConfiguration();
         ResolutionResult result = configuration.getIncoming().getResolutionResult();
         RenderableDependency root = new RenderableModuleResult(result.getRoot());
-
         renderNow(root);
-
-        resolvedConfiguration.rethrowFailure();
     }
 
     void renderNow(RenderableDependency root) {
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/DependencyGraphRenderer.groovy b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/DependencyGraphRenderer.groovy
index 0473c9f..caf2438 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/DependencyGraphRenderer.groovy
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/DependencyGraphRenderer.groovy
@@ -28,10 +28,9 @@ import static org.gradle.logging.StyledTextOutput.Style.Info
  * by Szczepan Faber, created at: 9/20/12
  */
 class DependencyGraphRenderer {
-
-    GraphRenderer renderer
-    NodeRenderer nodeRenderer
-    boolean hasCyclicDependencies = false
+    private final GraphRenderer renderer
+    private final NodeRenderer nodeRenderer
+    private boolean hasCyclicDependencies
 
     DependencyGraphRenderer(GraphRenderer renderer, NodeRenderer nodeRenderer) {
         this.renderer = renderer
@@ -41,41 +40,41 @@ class DependencyGraphRenderer {
     void render(RenderableDependency root) {
         def visited = new HashSet<ModuleVersionIdentifier>()
         visited.add(root.getId())
-        renderChildren(root.getChildren(), visited);
+        renderChildren(root.getChildren(), visited)
     }
 
     private void renderChildren(Set<? extends RenderableDependency> children, Set<ModuleVersionIdentifier> visited) {
-        renderer.startChildren();
-        int i = 0;
+        renderer.startChildren()
+        def i = 0
         for (RenderableDependency child : children) {
-            boolean last = i++ == children.size() - 1;
-            render(child, last, visited);
+            boolean last = i++ == children.size() - 1
+            render(child, last, visited)
         }
-        renderer.completeChildren();
+        renderer.completeChildren()
     }
 
-    private void render(final RenderableDependency parent, boolean last, Set<ModuleVersionIdentifier> visited) {
-        def children = parent.getChildren();
-        boolean alreadyRendered = !visited.add(parent.getId())
+    private void render(final RenderableDependency node, boolean last, Set<ModuleVersionIdentifier> visited) {
+        def children = node.getChildren()
+        def alreadyRendered = !visited.add(node.getId())
         if (alreadyRendered) {
             hasCyclicDependencies = true
         }
 
         renderer.visit(new Action<StyledTextOutput>() {
-            public void execute(StyledTextOutput output) {
-                nodeRenderer.renderNode(output, parent, children, alreadyRendered);
+            void execute(StyledTextOutput output) {
+                nodeRenderer.renderNode(output, node, alreadyRendered)
             }
-        }, last);
+        }, last)
 
         if (!alreadyRendered) {
-            renderChildren(children, visited);
+            renderChildren(children, visited)
         }
     }
 
     void printLegend() {
         if (hasCyclicDependencies) {
             renderer.output.println()
-            renderer.output.withStyle(Info).println("(*) - dependencies omitted (listed previously)");
+            renderer.output.withStyle(Info).println("(*) - dependencies omitted (listed previously)")
         }
     }
 }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/NodeRenderer.groovy b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/NodeRenderer.groovy
index ee4f888..eb4ddc1 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/NodeRenderer.groovy
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/NodeRenderer.groovy
@@ -23,6 +23,5 @@ import org.gradle.logging.StyledTextOutput
  * by Szczepan Faber, created at: 9/20/12
  */
 interface NodeRenderer {
-    void renderNode(StyledTextOutput output, RenderableDependency node,
-                    Set<RenderableDependency>children, boolean alreadyRendered);
+    void renderNode(StyledTextOutput output, RenderableDependency node, boolean alreadyRendered);
 }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/SimpleNodeRenderer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/SimpleNodeRenderer.java
index b9a743b..2e8b5b3 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/SimpleNodeRenderer.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/SimpleNodeRenderer.java
@@ -19,17 +19,18 @@ package org.gradle.api.tasks.diagnostics.internal.graph;
 import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableDependency;
 import org.gradle.logging.StyledTextOutput;
 
-import java.util.Set;
-
+import static org.gradle.logging.StyledTextOutput.Style.Failure;
 import static org.gradle.logging.StyledTextOutput.Style.Info;
 
 /**
-* by Szczepan Faber, created at: 9/21/12
-*/
+ * by Szczepan Faber, created at: 9/21/12
+ */
 public class SimpleNodeRenderer implements NodeRenderer {
-    public void renderNode(StyledTextOutput output, RenderableDependency node, Set<RenderableDependency> children, boolean alreadyRendered) {
+    public void renderNode(StyledTextOutput output, RenderableDependency node, boolean alreadyRendered) {
         output.text(node.getName());
-        if (alreadyRendered) {
+        if (!node.isResolvable()) {
+            output.withStyle(Failure).text(" FAILED");
+        } else if (alreadyRendered && !node.getChildren().isEmpty()) {
             output.withStyle(Info).text(" (*)");
         }
     }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResult.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResult.java
index 84abb38..b1d4122 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResult.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResult.java
@@ -13,70 +13,56 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.gradle.api.tasks.diagnostics.internal.graph.nodes;
 
+import org.gradle.api.Nullable;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.artifacts.result.ResolvedDependencyResult;
-
-import java.util.Set;
 
-/**
- * by Szczepan Faber, created at: 7/27/12
- */
 public abstract class AbstractRenderableDependencyResult implements RenderableDependency {
-
-    protected final ResolvedDependencyResult dependency;
-    protected final String description;
-
-    public AbstractRenderableDependencyResult(ResolvedDependencyResult dependency, String description) {
-        this.dependency = dependency;
-        this.description = description;
+    public ModuleVersionIdentifier getId() {
+        return getActual();
     }
 
     public String getName() {
-        if (!requestedEqualsSelected(dependency)) {
-            return getVerboseName();
-        } else {
-            return requested();
+        if (requestedEqualsSelected()) {
+            return getSimpleName();
         }
+        return getVerboseName();
     }
 
-    private String getVerboseName() {
-        ModuleVersionSelector requested = dependency.getRequested();
-        ModuleVersionIdentifier selected = dependency.getSelected().getId();
-        if(!selected.getGroup().equals(requested.getGroup())) {
-            return requested() + " -> " + selected.getGroup() + ":" + selected.getName() + ":" + selected.getVersion();
-        } else if (!selected.getName().equals(requested.getName())) {
-            return requested() + " -> " + selected.getName() + ":" + selected.getVersion();
-        } else if (!selected.getVersion().equals(requested.getVersion())) {
-            return requested() + " -> " + selected.getVersion();
-        } else {
-            return requested();
-        }
-    }
+    public abstract boolean isResolvable();
 
-    private static boolean requestedEqualsSelected(ResolvedDependencyResult dependency) {
-        return dependency.getRequested().matchesStrictly(dependency.getSelected().getId());
+    private boolean requestedEqualsSelected() {
+        return getRequested().matchesStrictly(getActual());
     }
 
-    private String requested() {
-        return dependency.getRequested().getGroup() + ":" + dependency.getRequested().getName() + ":" + dependency.getRequested().getVersion();
+    @Nullable
+    public String getDescription() {
+        return null;
     }
 
-    public ModuleVersionIdentifier getId() {
-        return dependency.getSelected().getId();
+    private String getSimpleName() {
+        ModuleVersionSelector requested = getRequested();
+        return requested.getGroup() + ":" + requested.getName() + ":" + requested.getVersion();
     }
 
-    public String getDescription() {
-        return description;
+    private String getVerboseName() {
+        ModuleVersionSelector requested = getRequested();
+        ModuleVersionIdentifier selected = getActual();
+        if(!selected.getGroup().equals(requested.getGroup())) {
+            return getSimpleName() + " -> " + selected.getGroup() + ":" + selected.getName() + ":" + selected.getVersion();
+        }
+        if (!selected.getName().equals(requested.getName())) {
+            return getSimpleName() + " -> " + selected.getName() + ":" + selected.getVersion();
+        }
+        if (!selected.getVersion().equals(requested.getVersion())) {
+            return getSimpleName() + " -> " + selected.getVersion();
+        }
+        return getSimpleName();
     }
 
-    public abstract Set<RenderableDependency> getChildren();
+    protected abstract ModuleVersionSelector getRequested();
 
-    @Override
-    public String toString() {
-        return dependency.toString();
-    }
+    protected abstract ModuleVersionIdentifier getActual();
 }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableModuleResult.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableModuleResult.java
index 1bb911e..95a3606 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableModuleResult.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableModuleResult.java
@@ -44,6 +44,10 @@ public abstract class AbstractRenderableModuleResult implements RenderableDepend
         return null;
     }
 
+    public boolean isResolvable() {
+        return true; // TODO
+    }
+
     public abstract Set<RenderableDependency> getChildren();
 
     @Override
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/DependencyEdge.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/DependencyEdge.java
new file mode 100644
index 0000000..98669a2
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/DependencyEdge.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks.diagnostics.internal.graph.nodes;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+
+import java.util.Set;
+
+public interface DependencyEdge {
+    boolean isResolvable();
+
+    ModuleVersionSelector getRequested();
+
+    ModuleVersionIdentifier getActual();
+
+    ModuleVersionIdentifier getFrom();
+
+    ModuleVersionSelectionReason getReason();
+
+    Set<? extends RenderableDependency> getChildren();
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableDependencyResult.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableDependencyResult.java
deleted file mode 100644
index 33fee40..0000000
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableDependencyResult.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.tasks.diagnostics.internal.graph.nodes;
-
-import org.gradle.api.artifacts.result.ResolvedDependencyResult;
-
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-/**
- * Children of this renderable dependency node are its dependents.
- *
- * by Szczepan Faber, created at: 7/27/12
- */
-public class InvertedRenderableDependencyResult extends RenderableDependencyResult implements RenderableDependency {
-
-    public InvertedRenderableDependencyResult(ResolvedDependencyResult dependency, String description) {
-        super(dependency, description);
-    }
-
-    public Set<RenderableDependency> getChildren() {
-        Set<RenderableDependency> out = new LinkedHashSet<RenderableDependency>();
-        for (ResolvedDependencyResult r : dependency.getSelected().getDependents()) {
-            //we want only the dependents that match the requested
-            if (r.getRequested().equals(this.dependency.getRequested())) {
-                out.add(new InvertedRenderableModuleResult(r.getFrom()));
-            }
-        }
-
-        return out;
-    }
-}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableModuleResult.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableModuleResult.java
index ce59349..4f3bc33 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableModuleResult.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableModuleResult.java
@@ -16,12 +16,13 @@
 
 package org.gradle.api.tasks.diagnostics.internal.graph.nodes;
 
-import com.google.common.base.Function;
-import com.google.common.collect.Collections2;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.result.ResolvedDependencyResult;
 import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
 
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -29,17 +30,20 @@ import java.util.Set;
  *
  * by Szczepan Faber, created at: 8/10/12
  */
-public class InvertedRenderableModuleResult extends RenderableModuleResult implements RenderableDependency {
+public class InvertedRenderableModuleResult extends RenderableModuleResult {
 
     public InvertedRenderableModuleResult(ResolvedModuleVersionResult module) {
         super(module);
     }
 
     public Set<RenderableDependency> getChildren() {
-        return new LinkedHashSet(Collections2.transform(module.getDependents(), new Function<ResolvedDependencyResult, RenderableDependency>() {
-            public RenderableDependency apply(ResolvedDependencyResult input) {
-                return new InvertedRenderableModuleResult(input.getFrom());
+        Map<ModuleVersionIdentifier, RenderableDependency> children = new LinkedHashMap<ModuleVersionIdentifier, RenderableDependency>();
+        for (ResolvedDependencyResult dependent : module.getDependents()) {
+            InvertedRenderableModuleResult child = new InvertedRenderableModuleResult(dependent.getFrom());
+            if (!children.containsKey(child.getId())) {
+                children.put(child.getId(), child);
             }
-        }));
+        }
+        return new LinkedHashSet<RenderableDependency>(children.values());
     }
 }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependency.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependency.java
index 7a4713a..d5695b4 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependency.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependency.java
@@ -27,5 +27,6 @@ public interface RenderableDependency {
     ModuleVersionIdentifier getId();
     String getName();
     String getDescription();
-    Set<RenderableDependency> getChildren();
+    boolean isResolvable();
+    Set<? extends RenderableDependency> getChildren();
 }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependencyResult.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependencyResult.java
index 59cdbcb..e5f697a 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependencyResult.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependencyResult.java
@@ -16,8 +16,11 @@
 
 package org.gradle.api.tasks.diagnostics.internal.graph.nodes;
 
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.result.DependencyResult;
 import org.gradle.api.artifacts.result.ResolvedDependencyResult;
+import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
 
 import java.util.LinkedHashSet;
 import java.util.Set;
@@ -25,21 +28,34 @@ import java.util.Set;
 /**
  * by Szczepan Faber, created at: 7/27/12
  */
-public class RenderableDependencyResult extends AbstractRenderableDependencyResult implements RenderableDependency {
+public class RenderableDependencyResult extends AbstractRenderableDependencyResult {
+    private final ResolvedDependencyResult dependency;
 
     public RenderableDependencyResult(ResolvedDependencyResult dependency) {
-        this(dependency, null);
+        this.dependency = dependency;
     }
 
-    public RenderableDependencyResult(ResolvedDependencyResult dependency, String description) {
-        super(dependency, description);
+    @Override
+    public boolean isResolvable() {
+        return true;
+    }
+
+    @Override
+    protected ModuleVersionIdentifier getActual() {
+        return dependency.getSelected().getId();
+    }
+
+    @Override
+    protected ModuleVersionSelector getRequested() {
+        return dependency.getRequested();
     }
 
     public Set<RenderableDependency> getChildren() {
         Set<RenderableDependency> out = new LinkedHashSet<RenderableDependency>();
         for (DependencyResult d : dependency.getSelected().getDependencies()) {
-            //TODO SF revisit when implementing the 'unresolved dependencies' story
-            if (d instanceof ResolvedDependencyResult) {
+            if (d instanceof UnresolvedDependencyResult) {
+                out.add(new RenderableUnresolvedDependencyResult((UnresolvedDependencyResult) d));
+            } else {
                 out.add(new RenderableDependencyResult((ResolvedDependencyResult) d));
             }
         }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableModuleResult.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableModuleResult.java
index a693e1f..20c2a4b 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableModuleResult.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableModuleResult.java
@@ -19,6 +19,7 @@ package org.gradle.api.tasks.diagnostics.internal.graph.nodes;
 import org.gradle.api.artifacts.result.DependencyResult;
 import org.gradle.api.artifacts.result.ResolvedDependencyResult;
 import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
+import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
 
 import java.util.LinkedHashSet;
 import java.util.Set;
@@ -26,7 +27,7 @@ import java.util.Set;
 /**
  * by Szczepan Faber, created at: 8/10/12
  */
-public class RenderableModuleResult extends AbstractRenderableModuleResult implements RenderableDependency {
+public class RenderableModuleResult extends AbstractRenderableModuleResult {
 
     public RenderableModuleResult(ResolvedModuleVersionResult module) {
         super(module);
@@ -35,8 +36,9 @@ public class RenderableModuleResult extends AbstractRenderableModuleResult imple
     public Set<RenderableDependency> getChildren() {
         Set<RenderableDependency> out = new LinkedHashSet<RenderableDependency>();
         for (DependencyResult d : module.getDependencies()) {
-            //TODO SF revisit when implementing the 'unresolved dependencies' story
-            if (d instanceof ResolvedDependencyResult) {
+            if (d instanceof UnresolvedDependencyResult) {
+                out.add(new RenderableUnresolvedDependencyResult((UnresolvedDependencyResult) d));
+            } else {
                 out.add(new RenderableDependencyResult((ResolvedDependencyResult) d));
             }
         }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableUnresolvedDependencyResult.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableUnresolvedDependencyResult.java
new file mode 100644
index 0000000..555e930
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableUnresolvedDependencyResult.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks.diagnostics.internal.graph.nodes;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class RenderableUnresolvedDependencyResult extends AbstractRenderableDependencyResult {
+    private final ModuleVersionIdentifier actual;
+    private final UnresolvedDependencyResult dependency;
+
+    public RenderableUnresolvedDependencyResult(UnresolvedDependencyResult dependency) {
+        this.dependency = dependency;
+        ModuleVersionSelector attempted = dependency.getAttempted();
+        this.actual = DefaultModuleVersionIdentifier.newId(attempted.getGroup(), attempted.getName(), attempted.getVersion());
+    }
+
+    @Override
+    public boolean isResolvable() {
+        return false;
+    }
+
+    @Override
+    protected ModuleVersionSelector getRequested() {
+        return dependency.getRequested();
+    }
+
+    @Override
+    protected ModuleVersionIdentifier getActual() {
+        return actual;
+    }
+
+    public Set<RenderableDependency> getChildren() {
+        return Collections.emptySet();
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RequestedVersion.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RequestedVersion.java
new file mode 100644
index 0000000..7d3365e
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RequestedVersion.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks.diagnostics.internal.graph.nodes;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class RequestedVersion extends AbstractRenderableDependencyResult {
+    private final ModuleVersionSelector requested;
+    private final ModuleVersionIdentifier actual;
+    private final boolean resolvable;
+    private final String description;
+    private final Set<RenderableDependency> children = new LinkedHashSet<RenderableDependency>();
+
+    public RequestedVersion(ModuleVersionIdentifier actual, boolean resolvable, String description) {
+        this(DefaultModuleVersionSelector.newSelector(actual.getGroup(), actual.getName(), actual.getVersion()), actual, resolvable, description);
+    }
+
+    public RequestedVersion(ModuleVersionSelector requested, ModuleVersionIdentifier actual, boolean resolvable, String description) {
+        this.requested = requested;
+        this.actual = actual;
+        this.resolvable = resolvable;
+        this.description = description;
+    }
+
+    public void addChild(DependencyEdge child) {
+        children.addAll(child.getChildren());
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    @Override
+    protected ModuleVersionIdentifier getActual() {
+        return actual;
+    }
+
+    @Override
+    protected ModuleVersionSelector getRequested() {
+        return requested;
+    }
+
+    @Override
+    public boolean isResolvable() {
+        return resolvable;
+    }
+
+    public Set<RenderableDependency> getChildren() {
+        return children;
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/ResolvedDependencyEdge.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/ResolvedDependencyEdge.java
new file mode 100644
index 0000000..d24c548
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/ResolvedDependencyEdge.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks.diagnostics.internal.graph.nodes;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.result.ResolvedDependencyResult;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class ResolvedDependencyEdge implements DependencyEdge {
+    private final ResolvedDependencyResult dependency;
+
+    public ResolvedDependencyEdge(ResolvedDependencyResult dependency) {
+        this.dependency = dependency;
+    }
+
+    public boolean isResolvable() {
+        return true;
+    }
+
+    public ModuleVersionSelector getRequested() {
+        return dependency.getRequested();
+    }
+
+    public ModuleVersionSelectionReason getReason() {
+        return dependency.getSelected().getSelectionReason();
+    }
+
+    public ModuleVersionIdentifier getActual() {
+        return dependency.getSelected().getId();
+    }
+
+    public ModuleVersionIdentifier getFrom() {
+        return dependency.getFrom().getId();
+    }
+
+    public Set<? extends RenderableDependency> getChildren() {
+        return Collections.singleton(new InvertedRenderableModuleResult(dependency.getFrom()));
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/SimpleDependency.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/SimpleDependency.java
index 590250c..5517bac 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/SimpleDependency.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/SimpleDependency.java
@@ -27,15 +27,17 @@ public class SimpleDependency implements RenderableDependency {
 
     private final ModuleVersionIdentifier id;
     private final String name;
+    private final boolean resolvable;
     private final String description;
     private final Set<RenderableDependency> children = new LinkedHashSet<RenderableDependency>();
 
     public SimpleDependency(String name) {
-        this(name, null);
+        this(name, true, null);
     }
 
-    public SimpleDependency(String name, String description) {
+    public SimpleDependency(String name, boolean resolvable, String description) {
         this.name = name;
+        this.resolvable = resolvable;
         this.description = description;
         this.id = newId(name, name, "1.0");
     }
@@ -48,6 +50,10 @@ public class SimpleDependency implements RenderableDependency {
         return name;
     }
 
+    public boolean isResolvable() {
+        return resolvable;
+    }
+
     public String getDescription() {
         return description;
     }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/UnresolvedDependencyEdge.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/UnresolvedDependencyEdge.java
new file mode 100644
index 0000000..41ffe55
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/UnresolvedDependencyEdge.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks.diagnostics.internal.graph.nodes;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class UnresolvedDependencyEdge implements DependencyEdge {
+    private final UnresolvedDependencyResult dependency;
+    private final ModuleVersionIdentifier actual;
+
+    public UnresolvedDependencyEdge(UnresolvedDependencyResult dependency) {
+        this.dependency = dependency;
+        ModuleVersionSelector attempted = dependency.getAttempted();
+        actual = DefaultModuleVersionIdentifier.newId(attempted.getGroup(), attempted.getName(), attempted.getVersion());
+    }
+
+    public boolean isResolvable() {
+        return false;
+    }
+
+    public ModuleVersionSelector getRequested() {
+        return dependency.getRequested();
+    }
+
+    public ModuleVersionIdentifier getActual() {
+        return actual;
+    }
+
+    public ModuleVersionSelectionReason getReason() {
+        return dependency.getAttemptedReason();
+    }
+
+    public ModuleVersionIdentifier getFrom() {
+        return dependency.getFrom().getId();
+    }
+
+    public Set<? extends RenderableDependency> getChildren() {
+        return Collections.singleton(new InvertedRenderableModuleResult(dependency.getFrom()));
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyInsightReporter.groovy b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyInsightReporter.groovy
index af1f9a1..f9258de 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyInsightReporter.groovy
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyInsightReporter.groovy
@@ -18,11 +18,10 @@ package org.gradle.api.tasks.diagnostics.internal.insight;
 
 
 import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.artifacts.result.DependencyResult
 import org.gradle.api.artifacts.result.ModuleVersionSelectionReason
-import org.gradle.api.artifacts.result.ResolvedDependencyResult
-import org.gradle.api.tasks.diagnostics.internal.graph.nodes.InvertedRenderableDependencyResult
-import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableDependency
-import org.gradle.api.tasks.diagnostics.internal.graph.nodes.SimpleDependency
+import org.gradle.api.artifacts.result.UnresolvedDependencyResult
+import org.gradle.api.tasks.diagnostics.internal.graph.nodes.*
 
 /**
  * Created: 23/08/2012
@@ -31,37 +30,47 @@ import org.gradle.api.tasks.diagnostics.internal.graph.nodes.SimpleDependency
  */
 public class DependencyInsightReporter {
 
-    Collection<RenderableDependency> prepare(Collection<ResolvedDependencyResult> input) {
+    Collection<RenderableDependency> prepare(Collection<DependencyResult> input) {
         def out = new LinkedList<RenderableDependency>()
-        def sorted = ResolvedDependencyResultSorter.sort(input)
+        def dependencies = input.collect {
+            if (it instanceof UnresolvedDependencyResult) {
+                return new UnresolvedDependencyEdge(it)
+            } else {
+                return new ResolvedDependencyEdge(it)
+            }
+        }
+
+        def sorted = DependencyResultSorter.sort(dependencies)
 
         //remember if module id was annotated
         def annotated = new HashSet<ModuleVersionIdentifier>()
+        def current = null
 
-        for (ResolvedDependencyResult dependency: sorted) {
-            def description = null
+        for (DependencyEdge dependency: sorted) {
             //add description only to the first module
-            if (annotated.add(dependency.selected.id)) {
+            if (annotated.add(dependency.actual)) {
                 //add a heading dependency with the annotation if the dependency does not exist in the graph
-                if (!dependency.requested.matchesStrictly(dependency.selected.id)) {
-                    def name = dependency.selected.id.group + ":" + dependency.selected.id.name + ":" + dependency.selected.id.version
-                    out << new SimpleDependency(name, describeReason(dependency.selected.selectionReason))
+                if (!dependency.requested.matchesStrictly(dependency.actual)) {
+                    out << new RequestedVersion(dependency.actual, dependency.resolvable, describeReason(dependency.reason))
+                    current = new RequestedVersion(dependency.requested, dependency.actual, dependency.resolvable, null)
+                    out << current
                 } else {
-                    description = describeReason(dependency.selected.selectionReason)
+                    current = new RequestedVersion(dependency.requested, dependency.actual, dependency.resolvable, describeReason(dependency.reason))
+                    out << current
                 }
+            } else if (current.requested != dependency.requested) {
+                current = new RequestedVersion(dependency.requested, dependency.actual, dependency.resolvable, null)
+                out << current
             }
-
-            out << new InvertedRenderableDependencyResult(dependency, description)
+            current.addChild(dependency)
         }
 
         out
     }
 
     private String describeReason(ModuleVersionSelectionReason reason) {
-        if (reason.conflictResolution) {
-            return "conflict resolution"
-        } else if (reason.forced) {
-            return "forced"
+        if (reason.conflictResolution || reason.forced || reason.selectedByRule) {
+            return reason.description
         } else {
             return null
         }
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyResultSorter.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyResultSorter.java
new file mode 100644
index 0000000..ba99512
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyResultSorter.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks.diagnostics.internal.insight;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.internal.artifacts.version.LatestVersionSemanticComparator;
+import org.gradle.api.tasks.diagnostics.internal.graph.nodes.DependencyEdge;
+
+import java.util.*;
+
+/**
+ * Created: 17/08/2012
+ *
+ * @author Szczepan Faber
+ */
+public class DependencyResultSorter {
+
+    /**
+     * sorts by group:name:version mostly.
+     * If requested matches selected then it will override the version comparison
+     * so that the dependency that was selected is more prominent.
+     */
+    public static Collection<DependencyEdge> sort(Collection<DependencyEdge> input) {
+        List<DependencyEdge> out = new ArrayList<DependencyEdge>(input);
+        Collections.sort(out, new DependencyComparator());
+        return out;
+    }
+
+    private static class DependencyComparator implements Comparator<DependencyEdge> {
+
+        private final LatestVersionSemanticComparator versionComparator = new LatestVersionSemanticComparator();
+
+        public int compare(DependencyEdge left, DependencyEdge right) {
+            ModuleVersionSelector leftRequested = left.getRequested();
+            ModuleVersionSelector rightRequested = right.getRequested();
+            int byGroup = leftRequested.getGroup().compareTo(rightRequested.getGroup());
+            if (byGroup != 0) {
+                return byGroup;
+            }
+
+            int byModule = leftRequested.getName().compareTo(rightRequested.getName());
+            if (byModule != 0) {
+                return byModule;
+            }
+
+            //if selected matches requested version comparison is overridden
+            boolean leftMatches = leftRequested.matchesStrictly(left.getActual());
+            boolean rightMatches = rightRequested.matchesStrictly(right.getActual());
+            if (leftMatches && !rightMatches) {
+                return -1;
+            } else if (!leftMatches && rightMatches) {
+                return 1;
+            }
+
+            int byVersion = versionComparator.compare(leftRequested.getVersion(), rightRequested.getVersion());
+            if (byVersion != 0) {
+                return byVersion;
+            }
+
+            ModuleVersionIdentifier leftFrom = left.getFrom();
+            ModuleVersionIdentifier rightFrom = right.getFrom();
+            byGroup = leftFrom.getGroup().compareTo(rightFrom.getGroup());
+            if (byGroup != 0) {
+                return byGroup;
+            }
+
+            byModule = leftFrom.getName().compareTo(rightFrom.getName());
+            if (byModule != 0) {
+                return byModule;
+            }
+
+            return versionComparator.compare(leftFrom.getVersion(), rightFrom.getVersion());
+        }
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/ResolvedDependencyResultSorter.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/ResolvedDependencyResultSorter.java
deleted file mode 100644
index 4e9c4a3..0000000
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/ResolvedDependencyResultSorter.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.tasks.diagnostics.internal.insight;
-
-import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.artifacts.result.ResolvedDependencyResult;
-import org.gradle.api.internal.artifacts.version.LatestVersionSemanticComparator;
-import org.gradle.api.specs.Spec;
-import org.gradle.util.CollectionUtils;
-
-import java.util.*;
-
-/**
- * Created: 17/08/2012
- *
- * @author Szczepan Faber
- */
-public class ResolvedDependencyResultSorter {
-
-    /**
-     * sorts by group:name:version mostly.
-     * If requested matches selected then it will override the version comparison
-     * so that the dependency that was selected is more prominent.
-     */
-    public static Collection<ResolvedDependencyResult> sort(Collection<ResolvedDependencyResult> input) {
-        //dependencies with the same 'requested' should be presented in a single tree
-        final Set<ModuleVersionSelector> uniqueRequested = new HashSet<ModuleVersionSelector>();
-        List<ResolvedDependencyResult> out = CollectionUtils.filter(input, new LinkedList<ResolvedDependencyResult>(), new Spec<ResolvedDependencyResult>() {
-            public boolean isSatisfiedBy(ResolvedDependencyResult element) {
-                return uniqueRequested.add(element.getRequested());
-            }
-        });
-        Collections.sort(out, new DependencyComparator());
-        return out;
-    }
-
-    private static class DependencyComparator implements Comparator<ResolvedDependencyResult> {
-
-        private final LatestVersionSemanticComparator versionComparator = new LatestVersionSemanticComparator();
-
-        public int compare(ResolvedDependencyResult left, ResolvedDependencyResult right) {
-            int byGroup = left.getRequested().getGroup().compareTo(right.getRequested().getGroup());
-            if (byGroup != 0) {
-                return byGroup;
-            }
-
-            int byModule = left.getRequested().getName().compareTo(right.getRequested().getName());
-            if (byModule != 0) {
-                return byModule;
-            }
-
-            //if selected matches requested version comparison is overridden
-            if (left.getRequested().matchesStrictly(left.getSelected().getId())) {
-                return -1;
-            } else if (right.getRequested().matchesStrictly(right.getSelected().getId())) {
-                return 1;
-            }
-
-            return versionComparator.compare(left.getRequested().getVersion(), right.getRequested().getVersion());
-        }
-    }
-}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTaskTest.java b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTaskTest.java
index ba362e8..179fd4d 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTaskTest.java
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTaskTest.java
@@ -19,8 +19,8 @@ import org.gradle.api.Project;
 import org.gradle.api.internal.project.DefaultProject;
 import org.gradle.api.tasks.diagnostics.internal.ReportRenderer;
 import org.gradle.logging.StyledTextOutput;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.util.HelperUtil;
-import org.gradle.util.TemporaryFolder;
 import org.gradle.util.WrapUtil;
 import org.jmock.Expectations;
 import org.jmock.Sequence;
@@ -46,7 +46,7 @@ public class AbstractReportTaskTest {
     private TestReportTask task;
     private ReportRenderer renderer;
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
     @Before
     public void setUp() throws Exception {
@@ -79,7 +79,7 @@ public class AbstractReportTaskTest {
 
     @Test
     public void setsOutputFileNameOnRendererBeforeGeneration() throws IOException {
-        final File file = tmpDir.getDir().file("report.txt");
+        final File file = tmpDir.getTestDirectory().file("report.txt");
 
         context.checking(new Expectations() {{
             Sequence sequence = context.sequence("sequence");
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTaskSpec.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTaskSpec.groovy
index 0a7c5b4..86499a9 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTaskSpec.groovy
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTaskSpec.groovy
@@ -16,25 +16,25 @@
 
 package org.gradle.api.tasks.diagnostics
 
+import org.gradle.api.InvalidUserDataException
 import org.gradle.api.specs.Spec
-import org.gradle.testfixtures.ProjectBuilder
+import org.gradle.util.HelperUtil
 import spock.lang.Specification
-import org.gradle.api.InvalidUserDataException
 
 /**
  * by Szczepan Faber, created at: 9/20/12
  */
 class DependencyInsightReportTaskSpec extends Specification {
 
-    def project = ProjectBuilder.builder().build()
-    def task = project.tasks.add("insight", DependencyInsightReportTask)
+    def project = HelperUtil.createRootProject()
+    def task = HelperUtil.createTask(DependencyInsightReportTask, project)
 
     def "fails if configuration missing"() {
         when:
         task.report()
 
         then:
-        thrown(ReportException)
+        thrown(InvalidUserDataException)
     }
 
     def "fails if dependency to include missing"() {
@@ -45,7 +45,7 @@ class DependencyInsightReportTaskSpec extends Specification {
         task.report()
 
         then:
-        thrown(ReportException)
+        thrown(InvalidUserDataException)
     }
 
     def "fails if dependency to include is empty"() {
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskTest.groovy
index e9dbf62..a17431a 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskTest.groovy
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskTest.groovy
@@ -15,19 +15,21 @@
  */
 package org.gradle.api.tasks.diagnostics;
 
-
 import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
 import org.gradle.api.tasks.diagnostics.internal.DependencyReportRenderer
 import org.gradle.api.tasks.diagnostics.internal.dependencies.AsciiDependencyReportRenderer
 import org.gradle.testfixtures.ProjectBuilder
 import org.gradle.util.HelperUtil
-import spock.lang.Specification
 
-public class DependencyReportTaskTest extends Specification {
+import spock.lang.Specification
 
+class DependencyReportTaskTest extends Specification {
     private Project project = new ProjectBuilder().build()
-    private DependencyReportTask task = HelperUtil.createTask(DependencyReportTask.class);
+    private DependencyReportTask task = HelperUtil.createTask(DependencyReportTask.class, project)
     private DependencyReportRenderer renderer = Mock(DependencyReportRenderer)
+    private Configuration conf1 = project.configurations.add("conf1")
+    private Configuration conf2 = project.configurations.add("conf2")
 
     void setup() {
         task.renderer = renderer
@@ -41,27 +43,23 @@ public class DependencyReportTaskTest extends Specification {
         task.configurations == null
     }
 
-    def "uses project configurations"() {
-        given:
-        def bConf = project.configurations.add("b-conf")
-        def aConf = project.configurations.add("a-conf")
-
+    def "renders all configurations in the project"() {
         when:
         task.generate(project)
 
-        then: 1 * renderer.startConfiguration(aConf)
-        then: 1 * renderer.render(aConf)
-        then: 1 * renderer.completeConfiguration(aConf)
+        then: 1 * renderer.startConfiguration(conf1)
+        then: 1 * renderer.render(conf1)
+        then: 1 * renderer.completeConfiguration(conf1)
 
 
-        then: 1 * renderer.startConfiguration(bConf)
-        then: 1 * renderer.render(bConf)
-        then: 1 * renderer.completeConfiguration(bConf)
+        then: 1 * renderer.startConfiguration(conf2)
+        then: 1 * renderer.render(conf2)
+        then: 1 * renderer.completeConfiguration(conf2)
 
         0 * renderer._
     }
 
-    def "uses specific configurations"() {
+    def "rendering can be limited to specific configurations"() {
         given:
         project.configurations.add("a")
         def bConf = project.configurations.add("b")
@@ -76,4 +74,16 @@ public class DependencyReportTaskTest extends Specification {
         1 * renderer.completeConfiguration(bConf)
         0 * renderer._
     }
+
+    def "rendering can be limited to a single configuration, specified by name"() {
+        given:
+        project.configurations.add("a")
+        def bConf = project.configurations.add("b")
+
+        when:
+        task.configuration = "b"
+
+        then:
+        task.configurations == [bConf] as Set
+    }
 }
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java
index 14cdae5..d208996 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java
@@ -87,6 +87,8 @@ public class TaskReportTaskTest {
             allowing(project).getDefaultTasks();
             will(returnValue(testDefaultTasks));
 
+            allowing(taskContainer).actualize();
+
             allowing(taskContainer).size();
             will(returnValue(4));
 
@@ -142,6 +144,8 @@ public class TaskReportTaskTest {
             allowing(project).getDefaultTasks();
             will(returnValue(defaultTasks));
 
+            allowing(taskContainer).actualize();
+
             allowing(taskContainer).size();
             will(returnValue(0));
 
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRendererTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRendererTest.groovy
index 12f4f23..2249d7a 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRendererTest.groovy
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRendererTest.groovy
@@ -19,7 +19,7 @@ package org.gradle.api.tasks.diagnostics.internal;
 import org.gradle.api.Project
 import org.gradle.logging.TestStyledTextOutput
 import org.gradle.logging.internal.StreamingStyledTextOutput
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.jmock.Expectations
 import org.jmock.integration.junit4.JMock
 import org.jmock.integration.junit4.JUnit4Mockery
@@ -37,12 +37,12 @@ import static org.junit.Assert.assertTrue
 public class TextReportRendererTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
     @Rule
-    public final TemporaryFolder testDir = new TemporaryFolder();
+    public final TestNameTestDirectoryProvider testDir = new TestNameTestDirectoryProvider();
     private final TextReportRenderer renderer = new TextReportRenderer();
 
     @Test
     public void writesReportToAFile() throws IOException {
-        File outFile = new File(testDir.getDir(), "report.txt");
+        File outFile = new File(testDir.getTestDirectory(), "report.txt");
         renderer.setOutputFile(outFile);
         assertThat(renderer.getTextOutput(), instanceOf(StreamingStyledTextOutput.class));
 
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/dependencies/AsciiDependencyReportRendererTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/dependencies/AsciiDependencyReportRendererTest.groovy
index ffb59ac..8fbda25 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/dependencies/AsciiDependencyReportRendererTest.groovy
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/dependencies/AsciiDependencyReportRendererTest.groovy
@@ -94,7 +94,7 @@ class AsciiDependencyReportRendererTest extends Specification {
     }
 
     def "informs if no dependencies"() {
-        def root = new SimpleDependency("root", "config")
+        def root = new SimpleDependency("root", true, "config")
 
         when:
         renderer.renderNow(root)
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResultSpec.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResultSpec.groovy
index 6cad400..aa30ef4 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResultSpec.groovy
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResultSpec.groovy
@@ -16,13 +16,12 @@
 
 package org.gradle.api.tasks.diagnostics.internal.graph.nodes
 
+import org.gradle.api.artifacts.ModuleVersionIdentifier
 import org.gradle.api.artifacts.ModuleVersionSelector
-import org.gradle.api.artifacts.result.ResolvedModuleVersionResult
-import org.gradle.api.internal.artifacts.result.DefaultResolvedDependencyResult
 import spock.lang.Specification
 
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
-import static org.gradle.api.internal.artifacts.result.ResolutionResultDataBuilder.newModule
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
 
 /**
  * by Szczepan Faber, created at: 10/9/12
@@ -34,13 +33,32 @@ class AbstractRenderableDependencyResultSpec extends Specification {
         def requested = newSelector('org.mockito', 'mockito-core', '1.0')
 
         expect:
-        dep(requested, newModule('org.mockito', 'mockito-core', '1.0')).name == 'org.mockito:mockito-core:1.0'
-        dep(requested, newModule('org.mockito', 'mockito-core', '2.0')).name == 'org.mockito:mockito-core:1.0 -> 2.0'
-        dep(requested, newModule('org.mockito', 'mockito', '1.0')).name == 'org.mockito:mockito-core:1.0 -> mockito:1.0'
-        dep(requested, newModule('com.mockito', 'mockito', '2.0')).name == 'org.mockito:mockito-core:1.0 -> com.mockito:mockito:2.0'
+        dep(requested, newId('org.mockito', 'mockito-core', '1.0')).name == 'org.mockito:mockito-core:1.0'
+        dep(requested, newId('org.mockito', 'mockito-core', '2.0')).name == 'org.mockito:mockito-core:1.0 -> 2.0'
+        dep(requested, newId('org.mockito', 'mockito', '1.0')).name == 'org.mockito:mockito-core:1.0 -> mockito:1.0'
+        dep(requested, newId('com.mockito', 'mockito', '2.0')).name == 'org.mockito:mockito-core:1.0 -> com.mockito:mockito:2.0'
     }
 
-    private RenderableDependency dep(ModuleVersionSelector requested, ResolvedModuleVersionResult selected) {
-        Spy(AbstractRenderableDependencyResult, constructorArgs: [new DefaultResolvedDependencyResult(requested, selected, newModule()), null])
+    private AbstractRenderableDependencyResult dep(ModuleVersionSelector requested, ModuleVersionIdentifier selected) {
+        return new AbstractRenderableDependencyResult() {
+            @Override
+            protected ModuleVersionSelector getRequested() {
+                return requested
+            }
+
+            @Override
+            protected ModuleVersionIdentifier getActual() {
+                return selected
+            }
+
+            @Override
+            boolean isResolvable() {
+                throw new UnsupportedOperationException()
+            }
+
+            Set<RenderableDependency> getChildren() {
+                throw new UnsupportedOperationException()
+            }
+        }
     }
 }
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableDependencyResultTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableDependencyResultTest.groovy
deleted file mode 100644
index 2d437c7..0000000
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableDependencyResultTest.groovy
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.tasks.diagnostics.internal.graph.nodes
-
-import org.gradle.api.internal.artifacts.result.DefaultResolvedDependencyResult
-import org.gradle.api.internal.artifacts.result.DefaultResolvedModuleVersionResult
-import spock.lang.Specification
-
-import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
-import static org.gradle.api.internal.artifacts.result.ResolutionResultDataBuilder.newModule
-
-/**
- * by Szczepan Faber, created at: 9/21/12
- */
-class InvertedRenderableDependencyResultTest extends Specification {
-
-    def "uses dependents as children"() {
-        expect:
-        /*
-        //at some point, find a better way to construct test data (graphs)
-        root->y->b(1.0)
-        root->x->b(1.0)
-        root->z->b(0.5)
-        */
-
-        def root = newModule("root")
-
-        def x = newDependency("org", "x", "1.0", "1.0", root)
-        def y = newDependency("org", "y", "1.0", "1.0", root)
-        def z = newDependency("org", "z", "1.0", "1.0", root)
-
-        def selectedB = newModule("org", "b", "1.0")
-
-        def b1 = newDependency("org", "b", "1.0", "1.0", x.selected, selectedB)
-        def b2 = newDependency("org", "b", "1.0", "1.0", y.selected, selectedB)
-        def b3 = newDependency("org", "b", "1.0", "0.5", z.selected, selectedB)
-
-        when:
-        def result = new InvertedRenderableDependencyResult(b3, null)
-
-        then:
-        result.children*.name == ['org:z:1.0']
-
-        when:
-        result = new InvertedRenderableDependencyResult(b2, null)
-
-        then:
-        result.children*.name == ['org:x:1.0', 'org:y:1.0']
-    }
-
-    static DefaultResolvedDependencyResult newDependency(String group='a', String module='a', String version='1', String requested = version,
-                                                         DefaultResolvedModuleVersionResult from = newModule(),
-                                                         DefaultResolvedModuleVersionResult selected = newModule(group, module, version)) {
-        def out = new DefaultResolvedDependencyResult(newSelector(group, module, requested), selected, from)
-        selected.addDependent(out)
-        out
-    }
-}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependencyResultTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependencyResultTest.groovy
new file mode 100644
index 0000000..3593418
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependencyResultTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks.diagnostics.internal.graph.nodes
+
+import org.gradle.api.artifacts.ModuleVersionSelector
+import org.gradle.api.artifacts.result.ResolvedModuleVersionResult
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+import static org.gradle.api.internal.artifacts.result.ResolutionResultDataBuilder.newModule
+import org.gradle.api.artifacts.result.ResolvedDependencyResult
+
+/**
+ * by Szczepan Faber, created at: 10/9/12
+ */
+class RenderableDependencyResultTest extends Specification {
+
+    def "renders name"() {
+        given:
+        def requested = newSelector('org.mockito', 'mockito-core', '1.0')
+        def same = newModule('org.mockito', 'mockito-core', '1.0')
+        def differentVersion = newModule('org.mockito', 'mockito-core', '2.0')
+        def differentName = newModule('org.mockito', 'mockito', '1.0')
+        def differentGroup = newModule('com.mockito', 'mockito', '2.0')
+
+        expect:
+        dep(requested, same).name == 'org.mockito:mockito-core:1.0'
+        dep(requested, differentVersion).name == 'org.mockito:mockito-core:1.0 -> 2.0'
+        dep(requested, differentName).name == 'org.mockito:mockito-core:1.0 -> mockito:1.0'
+        dep(requested, differentGroup).name == 'org.mockito:mockito-core:1.0 -> com.mockito:mockito:2.0'
+    }
+
+    private RenderableDependencyResult dep(ModuleVersionSelector requested, ResolvedModuleVersionResult selected) {
+        ResolvedDependencyResult dependencyResult = Stub() {
+            getRequested() >> requested
+            getSelected() >> selected
+        }
+        return new RenderableDependencyResult(dependencyResult)
+    }
+}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableUnresolvedDependencyResultTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableUnresolvedDependencyResultTest.groovy
new file mode 100644
index 0000000..65a5755
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableUnresolvedDependencyResultTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks.diagnostics.internal.graph.nodes
+
+import org.gradle.api.artifacts.ModuleVersionSelector
+import org.gradle.api.artifacts.result.UnresolvedDependencyResult
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+
+class RenderableUnresolvedDependencyResultTest extends Specification {
+
+    def "renders name"() {
+        given:
+        def requested = newSelector('org.mockito', 'mockito-core', '1.0')
+        def same = newSelector('org.mockito', 'mockito-core', '1.0')
+        def differentVersion = newSelector('org.mockito', 'mockito-core', '2.0')
+        def differentName = newSelector('org.mockito', 'mockito', '1.0')
+        def differentGroup = newSelector('com.mockito', 'mockito', '2.0')
+
+        expect:
+        dep(requested, same).name == 'org.mockito:mockito-core:1.0'
+        dep(requested, differentVersion).name == 'org.mockito:mockito-core:1.0 -> 2.0'
+        dep(requested, differentName).name == 'org.mockito:mockito-core:1.0 -> mockito:1.0'
+        dep(requested, differentGroup).name == 'org.mockito:mockito-core:1.0 -> com.mockito:mockito:2.0'
+    }
+
+    private RenderableUnresolvedDependencyResult dep(ModuleVersionSelector requested, ModuleVersionSelector attempted) {
+        UnresolvedDependencyResult dependencyResult = Stub() {
+            getRequested() >> requested
+            getAttempted() >> attempted
+        }
+        return new RenderableUnresolvedDependencyResult(dependencyResult)
+    }
+}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyResultSorterSpec.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyResultSorterSpec.groovy
new file mode 100644
index 0000000..4d49297
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyResultSorterSpec.groovy
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks.diagnostics.internal.insight
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.artifacts.ModuleVersionSelector
+import org.gradle.api.tasks.diagnostics.internal.graph.nodes.DependencyEdge
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+
+/**
+ * by Szczepan Faber, created at: 8/22/12
+ */
+class DependencyResultSorterSpec extends Specification {
+    def "sorts by requested version and prefers exact matching selected version over inexact"() {
+        def d1 = newDependency(newSelector("org.aha", "aha", "1.0"), newId("org.gradle", "zzzz", "3.0"))
+
+        def d2 = newDependency(newSelector("org.gradle", "core", "2.0"), newId("org.gradle", "core", "2.0"))
+        def d3 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"))
+        def d4 = newDependency(newSelector("org.gradle", "core", "1.5"), newId("org.gradle", "core", "2.0"))
+
+        def d5 = newDependency(newSelector("org.gradle", "xxxx", "1.0"), newId("org.gradle", "xxxx", "1.0"))
+
+        def d6 = newDependency(newSelector("org.gradle", "zzzz", "1.5"), newId("org.gradle", "zzzz", "3.0"))
+        def d7 = newDependency(newSelector("org.gradle", "zzzz", "2.0"), newId("org.gradle", "zzzz", "3.0"))
+
+        when:
+        def sorted = DependencyResultSorter.sort([d5, d3, d6, d1, d2, d7, d4])
+
+        then:
+        sorted == [d1, d2, d3, d4, d5, d6, d7]
+    }
+
+    def "semantically compares versions"() {
+        def d1 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"))
+        def d2 = newDependency(newSelector("org.gradle", "core", "1.0-alpha"), newId("org.gradle", "core", "2.0"))
+
+        when:
+        def sorted = DependencyResultSorter.sort([d1, d2])
+
+        then:
+        sorted == [d2, d1]
+    }
+
+    def "sorts by from when requested version is the same"() {
+        def d1 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"), newId("org.a", "a", "1.0"))
+        def d2 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"), newId("org.b", "a", "1.0"))
+        def d3 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"), newId("org.b", "b", "0.8"))
+        def d4 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"), newId("org.b", "b", "1.12"))
+        def d5 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"), newId("org.b", "b", "2.0"))
+        def d6 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"), newId("org.b", "c", "0.9"))
+        def d7 = newDependency(newSelector("org.gradle", "other", "1.0"), newId("org.gradle", "other", "1.0"), newId("org.b", "a", "0.9"))
+        def d8 = newDependency(newSelector("org.gradle", "other", "1.0"), newId("org.gradle", "other", "1.0"), newId("org.b", "a", "0.9.1"))
+
+        when:
+        def sorted = DependencyResultSorter.sort([d7, d8, d1, d3, d5, d2, d4, d6])
+
+
+        then:
+        sorted == [d1, d2, d3, d4, d5, d6, d7, d8]
+    }
+
+    private newDependency(ModuleVersionSelector requested, ModuleVersionIdentifier selected, ModuleVersionIdentifier from = newId("org", "a", "1.0")) {
+        return Stub(DependencyEdge) {
+            getRequested() >> requested
+            getActual() >> selected
+            getFrom() >> from
+        }
+    }
+}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/insight/ResolvedDependencyResultSorterSpec.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/insight/ResolvedDependencyResultSorterSpec.groovy
deleted file mode 100644
index a2da8fd..0000000
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/insight/ResolvedDependencyResultSorterSpec.groovy
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.tasks.diagnostics.internal.insight
-
-import org.gradle.api.artifacts.ModuleVersionIdentifier
-import org.gradle.api.artifacts.ModuleVersionSelector
-import org.gradle.api.internal.artifacts.result.DefaultResolvedDependencyResult
-import org.gradle.api.internal.artifacts.result.DefaultResolvedModuleVersionResult
-import spock.lang.Specification
-
-import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
-import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
-
-/**
- * by Szczepan Faber, created at: 8/22/12
- */
-class ResolvedDependencyResultSorterSpec extends Specification {
-
-    def "sorts"() {
-        def d1 = newDependency(newSelector("org.gradle", "core", "2.0"), newId("org.gradle", "core", "2.0"))
-        def d2 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"))
-        def d3 = newDependency(newSelector("org.gradle", "core", "1.5"), newId("org.gradle", "core", "2.0"))
-
-        def d4 = newDependency(newSelector("org.gradle", "xxxx", "1.0"), newId("org.gradle", "xxxx", "1.0"))
-
-        def d5 = newDependency(newSelector("org.gradle", "zzzz", "1.5"), newId("org.gradle", "zzzz", "3.0"))
-        def d6 = newDependency(newSelector("org.gradle", "zzzz", "2.0"), newId("org.gradle", "zzzz", "3.0"))
-
-        def d7 = newDependency(newSelector("org.aha", "aha", "1.0"), newId("org.gradle", "zzzz", "3.0"))
-
-        when:
-        def sorted = ResolvedDependencyResultSorter.sort([d5, d3, d6, d1, d2, d7, d4])
-
-        then:
-        sorted == [d7, d1, d2, d3, d4, d5, d6]
-    }
-
-    def "semantically compares versions"() {
-        def d1 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"))
-        def d2 = newDependency(newSelector("org.gradle", "core", "1.0-alpha"), newId("org.gradle", "core", "2.0"))
-
-        when:
-        def sorted = ResolvedDependencyResultSorter.sort([d1, d2])
-
-        then:
-        sorted == [d2, d1]
-    }
-
-    def "excludes dependencies with the same requested->selected"() {
-        def d1 = newDependency(newSelector("org.gradle", "core", "2.0"), newId("org.gradle", "core", "2.0"), "foo")
-        def d2 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"), "bar")
-        def d3 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"), "baz")
-
-        when:
-        def sorted = ResolvedDependencyResultSorter.sort([d3, d2, d1])
-
-        then:
-        sorted == [d1, d3]
-    }
-
-    private newDependency(ModuleVersionSelector requested, ModuleVersionIdentifier selected, String from = 'whatever') {
-        new DefaultResolvedDependencyResult(requested, new DefaultResolvedModuleVersionResult(selected),
-                new DefaultResolvedModuleVersionResult(newId("org", from, "1.0")))
-    }
-}
diff --git a/subprojects/distributions/distributions.gradle b/subprojects/distributions/distributions.gradle
index 37224a4..00ccf8f 100644
--- a/subprojects/distributions/distributions.gradle
+++ b/subprojects/distributions/distributions.gradle
@@ -39,7 +39,7 @@ configurations {
 
 integTestTasks.all {
     if (project.hasProperty("noDistTests")) {
-        enabled = false
+        gradle.startParameter.excludedTaskNames << it.path
     } else {
         inputs.files buildDists
     }
@@ -119,6 +119,7 @@ task srcZip(type: Zip) {
             include '*.gradle'
             include 'wrapper/'
             include 'gradlew.bat'
+            include 'version.txt'
         }
     }
 }
diff --git a/subprojects/distributions/src/integTest/groovy/org/gradle/AllDistributionIntegrationSpec.groovy b/subprojects/distributions/src/integTest/groovy/org/gradle/AllDistributionIntegrationSpec.groovy
index 2fe889f..b954efc 100644
--- a/subprojects/distributions/src/integTest/groovy/org/gradle/AllDistributionIntegrationSpec.groovy
+++ b/subprojects/distributions/src/integTest/groovy/org/gradle/AllDistributionIntegrationSpec.groovy
@@ -17,15 +17,20 @@
 package org.gradle
 
 import groovy.io.FileType
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 
 import static org.hamcrest.Matchers.containsString
 
 class AllDistributionIntegrationSpec extends DistributionIntegrationSpec {
 
+    @Override
+    String getDistributionLabel() {
+        "all"
+    }
+
     def allZipContents() {
         given:
-        TestFile contentsDir = unpackDistribution("all")
+        TestFile contentsDir = unpackDistribution()
 
         expect:
         checkMinimalContents(contentsDir)
diff --git a/subprojects/distributions/src/integTest/groovy/org/gradle/BinDistributionIntegrationSpec.groovy b/subprojects/distributions/src/integTest/groovy/org/gradle/BinDistributionIntegrationSpec.groovy
index ec59b7d..abfab6b 100644
--- a/subprojects/distributions/src/integTest/groovy/org/gradle/BinDistributionIntegrationSpec.groovy
+++ b/subprojects/distributions/src/integTest/groovy/org/gradle/BinDistributionIntegrationSpec.groovy
@@ -16,13 +16,18 @@
 
 package org.gradle
 
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 
 class BinDistributionIntegrationSpec extends DistributionIntegrationSpec {
 
+    @Override
+    String getDistributionLabel() {
+        "bin"
+    }
+
     def binZipContents() {
         given:
-        TestFile contentsDir = unpackDistribution("bin")
+        TestFile contentsDir = unpackDistribution()
 
         expect:
         checkMinimalContents(contentsDir)
diff --git a/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy b/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy
index 3522b4a..4233b87 100644
--- a/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy
+++ b/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy
@@ -16,33 +16,53 @@
 
 package org.gradle
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext
+import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.GradleVersion
 import org.gradle.util.PreconditionVerifier
-import org.gradle.util.TestFile
 import org.junit.Rule
 import spock.lang.Shared
-import spock.lang.Specification
+
+import java.util.zip.ZipEntry
+import java.util.zip.ZipFile
 
 import static org.hamcrest.Matchers.equalTo
 import static org.junit.Assert.assertThat
 
-class DistributionIntegrationSpec extends Specification {
+abstract class DistributionIntegrationSpec extends AbstractIntegrationSpec {
 
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
     @Rule public final PreconditionVerifier preconditionVerifier = new PreconditionVerifier()
 
     @Shared String version = GradleVersion.current().version
 
-    protected TestFile unpackDistribution(type) {
-        TestFile srcZip = dist.distributionsDir.file("gradle-$version-${type}.zip")
-        srcZip.usingNativeTools().unzipTo(dist.testDir)
-        TestFile contentsDir = dist.testDir.file("gradle-$version")
+    abstract String getDistributionLabel()
+
+    def "no duplicate entries"() {
+        given:
+        ZipFile zipFile = new ZipFile(zip)
+
+        when:
+        def entries = zipFile.entries().toList()
+        def entriesByPath = entries.groupBy { ZipEntry zipEntry -> zipEntry.name }
+        def dupes = entriesByPath.findAll() { it.value.size() > 1 }
+        def dupesWithCount = dupes.collectEntries { [it.key, it.value.size()]}
+
+        then:
+        dupesWithCount.isEmpty()
+    }
+
+    protected TestFile unpackDistribution(type = getDistributionLabel()) {
+        TestFile zip = getZip(type)
+        zip.usingNativeTools().unzipTo(testDirectory)
+        TestFile contentsDir = file("gradle-$version")
         contentsDir
     }
 
+    protected TestFile getZip(String type = getDistributionLabel()) {
+        new IntegrationTestBuildContext().distributionsDir.file("gradle-$version-${type}.zip")
+    }
+
     protected void checkMinimalContents(TestFile contentsDir) {
         // Check it can be executed
         executer.inDirectory(contentsDir).usingExecutable('bin/gradle').withTaskList().run()
@@ -59,8 +79,13 @@ class DistributionIntegrationSpec extends Specification {
         assert coreLibs.size() == 11
         coreLibs.each { assertIsGradleJar(it) }
         def wrapperJar = contentsDir.file("lib/gradle-wrapper-${version}.jar")
+        wrapperJar.assertIsFile()
         assert wrapperJar.length() < 20 * 1024; // wrapper needs to be small. Let's check it's smaller than some arbitrary 'small' limit
 
+        def toolingApiJar = contentsDir.file("lib/gradle-tooling-api-${version}.jar")
+        toolingApiJar.assertIsFile()
+        assert toolingApiJar.length() < 200 * 1024; // tooling api jar is the small plain tooling api jar version and not the fat jar.
+
         // Plugins
         assertIsGradleJar(contentsDir.file("lib/plugins/gradle-core-impl-${version}.jar"))
         assertIsGradleJar(contentsDir.file("lib/plugins/gradle-plugins-${version}.jar"))
diff --git a/subprojects/distributions/src/integTest/groovy/org/gradle/SrcDistributionIntegrationSpec.groovy b/subprojects/distributions/src/integTest/groovy/org/gradle/SrcDistributionIntegrationSpec.groovy
index 5531792..80ebc4e 100644
--- a/subprojects/distributions/src/integTest/groovy/org/gradle/SrcDistributionIntegrationSpec.groovy
+++ b/subprojects/distributions/src/integTest/groovy/org/gradle/SrcDistributionIntegrationSpec.groovy
@@ -17,17 +17,22 @@
 package org.gradle
 
 import org.apache.tools.ant.taskdefs.Expand
+import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.AntUtil
 import org.gradle.util.Requires
-import org.gradle.util.TestFile
 import org.gradle.util.TestPrecondition
 
 class SrcDistributionIntegrationSpec extends DistributionIntegrationSpec {
 
+    @Override
+    String getDistributionLabel() {
+        "src"
+    }
+
     @Requires(TestPrecondition.NOT_WINDOWS)
     def sourceZipContents() {
         given:
-        TestFile contentsDir = unpackDistribution("src")
+        TestFile contentsDir = unpackDistribution()
 
         expect:
         !contentsDir.file(".git").exists()
diff --git a/subprojects/docs/docs.gradle b/subprojects/docs/docs.gradle
index 56be49a..87bba4f 100755
--- a/subprojects/docs/docs.gradle
+++ b/subprojects/docs/docs.gradle
@@ -20,6 +20,9 @@ import org.gradle.build.docs.AssembleSamplesDocTask
 import org.gradle.build.docs.Docbook2Xhtml
 import org.gradle.build.docs.dsl.docbook.AssembleDslDocTask
 import org.gradle.build.docs.dsl.source.ExtractDslMetaDataTask
+import org.gradle.build.docs.releasenotes.*
+import org.gradle.build.docs.releasenotes.checks.*
+import org.apache.tools.ant.filters.ReplaceTokens
 
 apply plugin: 'base'
 apply plugin: 'pegdown'
@@ -27,13 +30,13 @@ apply plugin: 'jsoup'
 apply plugin: 'javascript-base'
 
 repositories {
-    add javaScript.googleApisRepository
+    javaScript.googleApis()
 
     ivy {
         name "Google Fonts"
         url "http://themes.googleusercontent.com/static/fonts/"
         layout 'pattern', {
-            artifact '[organisation]/v[revision]/[classifier](.[ext])'
+            artifact '[organisation]/v[revision](/[classifier])(.[ext])'
             ivy '[organisation]/v[revision]/ivy(.[ext])'
         }
     }
@@ -47,7 +50,7 @@ configurations {
 }
 
 dependencies {
-    userGuideTask 'xalan:xalan:2.7.1', 'xerces:xercesImpl:2.9.1'
+    userGuideTask 'xalan:xalan:2.7.1', libraries.xerces
     userGuideTask module('xhtmlrenderer:xhtmlrenderer:R8rc1') {
         dependency 'itext:itext:2.0.8 at jar'
     }
@@ -65,6 +68,13 @@ dependencies {
         "ubuntumono:regular-italic:3:KAKuHXAHZOeECOWAHsRKA-LrC4Du4e_yfTJ8Ol60xk0 at ttf",
         "ubuntumono:bold:3:ceqTZGKHipo8pJj4molytp_TkvowlIOtbR7ePgFOpF4 at ttf",
         "ubuntumono:bold-italic:3:n_d8tv_JOIiYyMXR4eaV9WsGzsqhEorxQDpu60nfWEc at ttf"
+
+    groovy libraries.groovy
+    testCompile "org.pegdown:pegdown:1.1.0"
+    testCompile "org.jsoup:jsoup:1.6.3"
+    testCompile "org.gebish:geb-spock:0.9.0-RC-1"
+    testCompile 'org.seleniumhq.selenium:selenium-htmlunit-driver:2.28.0'
+    testCompile project(":core")
 }
 
 ext {
@@ -87,7 +97,7 @@ outputs.distDocs = files(distDocsDir) {
     builtBy 'distDocs'
 }
 outputs.docs = files(docsDir) {
-    builtBy 'javadoc', 'groovydoc', 'userguide', 'dslHtml', 'releaseNotes'
+    builtBy 'javadocAll', 'groovydocAll', 'userguide', 'dslHtml', 'releaseNotes'
 }
 
 tasks.withType(Docbook2Xhtml) {
@@ -128,7 +138,7 @@ task configureCss << {
 
     ext.tokens = images + fonts
     css.inputs.property 'tokens', tokens
-    css.filter org.apache.tools.ant.filters.ReplaceTokens, tokens: tokens
+    css.filter ReplaceTokens, tokens: tokens
 }
 
 task css(type: Sync, dependsOn: configureCss) {
@@ -185,7 +195,7 @@ task samplesDocs(type: Docbook2Xhtml) {
 }
 
 task dslMetaData(type: ExtractDslMetaDataTask) {
-    source { groovydoc.source }
+    source { groovydocAll.source }
     destFile = new File(docbookSrc, 'dsl-meta-data.bin')
 }
 
@@ -268,7 +278,7 @@ task userguidePdf(type: Xhtml2Pdf, dependsOn: pdfUserguideXhtml) {
 def javaApiUrl = "http://docs.oracle.com/javase/1.5.0/docs/api/"
 def groovyApiUrl = "http://groovy.codehaus.org/gapi/"
 
-task javadoc(type: Javadoc) {
+task javadocAll(type: Javadoc) {
     ext.stylesheetFile = file("src/docs/css/javadoc.css")
     inputs.file stylesheetFile
 
@@ -295,9 +305,9 @@ task javadoc(type: Javadoc) {
     }
 }
 
-task configureGroovydoc {
+task configureGroovydocAll {
     doFirst {
-        project.configure(groovydoc) {
+        project.configure(groovydocAll) {
             [javaApiUrl, groovyApiUrl].each {
                 link(it, *(new URL("$it/package-list").text.tokenize("\n")))
             }
@@ -308,13 +318,13 @@ task configureGroovydoc {
     }
 }
 
-task groovydoc(type: Groovydoc, dependsOn: configureGroovydoc) {
+task groovydocAll(type: Groovydoc, dependsOn: configureGroovydocAll) {
     group = 'documentation'
     source publicGroovyProjects.collect {project -> project.sourceSets.main.groovy + project.sourceSets.main.java }
     destinationDir = new File(docsDir, 'groovydoc')
-    classpath = javadoc.classpath
-    includes = javadoc.includes
-    excludes = javadoc.excludes
+    classpath = javadocAll.classpath
+    includes = javadocAll.includes
+    excludes = javadocAll.excludes
     doFirst {
         windowTitle = "Gradle API $version"
         docTitle = windowTitle
@@ -327,7 +337,7 @@ task groovydoc(type: Groovydoc, dependsOn: configureGroovydoc) {
 }
 
 task checkstyleApi(type: Checkstyle) {
-    source javadoc.source
+    source javadocAll.source
     configFile = file("$checkstyleConfigDir/checkstyle-api.xml")
     classpath = files()
     reports.xml.destination = file("$checkstyle.reportsDir/checkstyle-api.xml")
@@ -375,15 +385,14 @@ task decorateReleaseNotes(type: Jsoup) {
     apply from: "release-notes-transform.gradle"
 }
 
-import org.apache.tools.ant.filters.*
 task releaseNotes(type: Copy) {
     group = "release notes"
     ext.fileName = "release-notes.html"
     into "$docsDir"
     from decorateReleaseNotes, {
-        rename ".+", "release-notes.html"
+        rename ".+", fileName
         doFirst {
-            owner.filter(ReplaceTokens, tokens: [version: project.version.toString()])
+            owner.filter(ReplaceTokens, tokens: [version: project.version.toString(), versionBase: rootProject.versionBase])
         }
     }
 }
@@ -395,8 +404,19 @@ task viewReleaseNotes(dependsOn: releaseNotes) {
     }
 }
 
+test {
+    dependsOn releaseNotes
+    systemProperty "org.gradle.docs.releasenotes.source", releaseNotesMarkdown.source.singleFile
+    systemProperty "org.gradle.docs.releasenotes.rendered", new File(releaseNotes.destinationDir, releaseNotes.fileName)
+
+}
+
+if (project.hasProperty('noDocsTests')) {
+    gradle.startParameter.excludedTaskNames << test.path
+}
+
 task docs {
-    dependsOn javadoc, groovydoc, userguide, distDocs, samplesDocs, dslHtml, releaseNotes
+    dependsOn javadocAll, groovydocAll, userguide, distDocs, samplesDocs, dslHtml, releaseNotes
     description = 'Generates all documentation'
     group = 'documentation'
 }
diff --git a/subprojects/docs/release-notes-transform.gradle b/subprojects/docs/release-notes-transform.gradle
index 80337d3..54e2346 100644
--- a/subprojects/docs/release-notes-transform.gradle
+++ b/subprojects/docs/release-notes-transform.gradle
@@ -146,7 +146,7 @@ decorateReleaseNotes {
             def name = topicHeading.text()
             def anchor = topicHeading.attr("id")
             
-            toc.append("<li><a href='#$anchor'>$name</a></li>").children().last()
+            toc.append("<li><a/></li>").children().last().select("a").first().text(name).attr("href", "#$anchor")
             
             def subs = topic.select("h3")
             if (subs) {
@@ -154,7 +154,7 @@ decorateReleaseNotes {
                 subs.each {
                     def subName = it.text()
                     def subAnchorName = it.attr("id")
-                    sublist.append("<li><a href='#$subAnchorName'>$subName</a></li>")
+                    sublist.append("<li><a/></li>").children().last().select("a").first().text(subName).attr("href", "#$subAnchorName")
                 }
             }
             
diff --git a/subprojects/docs/src/docs/dsl/dsl.xml b/subprojects/docs/src/docs/dsl/dsl.xml
index 0c51bcc..3089f13 100644
--- a/subprojects/docs/src/docs/dsl/dsl.xml
+++ b/subprojects/docs/src/docs/dsl/dsl.xml
@@ -161,10 +161,10 @@
                 <td>org.gradle.api.plugins.ExtensionAware</td>
             </tr>
             <tr>
-                <td>org.gradle.api.publish.PublishingExtension</td>
+                <td>org.gradle.api.plugins.ExtraPropertiesExtension</td>
             </tr>
             <tr>
-                <td>org.gradle.api.plugins.ExtraPropertiesExtension</td>
+                <td>org.gradle.api.publish.PublishingExtension</td>
             </tr>
             <tr>
                 <td>org.gradle.api.publish.ivy.IvyPublication</td>
@@ -172,6 +172,12 @@
             <tr>
                 <td>org.gradle.api.publish.ivy.IvyModuleDescriptor</td>
             </tr>
+            <tr>
+                <td>org.gradle.api.publish.maven.MavenPublication</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.publish.maven.MavenPom</td>
+            </tr>
         </table>
     </section>
 
@@ -232,6 +238,9 @@
                 <td>org.gradle.api.plugins.quality.CodeNarc</td>
             </tr>
             <tr>
+                <td>org.gradle.api.plugins.buildcomparison.gradle.CompareGradleBuilds</td>
+            </tr>
+            <tr>
                 <td>org.gradle.api.tasks.compile.Compile</td>
             </tr>
             <tr>
@@ -256,6 +265,9 @@
                 <td>org.gradle.api.plugins.quality.FindBugs</td>
             </tr>
             <tr>
+                <td>org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor</td>
+            </tr>
+            <tr>
                 <td>org.gradle.api.tasks.GradleBuild</td>
             </tr>
             <tr>
@@ -292,6 +304,12 @@
                 <td>org.gradle.api.plugins.quality.Pmd</td>
             </tr>
             <tr>
+                <td>org.gradle.api.publish.ivy.tasks.PublishToIvyRepository</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.publish.maven.tasks.PublishToMavenRepository</td>
+            </tr>
+            <tr>
                 <td>org.gradle.api.tasks.scala.ScalaCompile</td>
             </tr>
             <tr>
@@ -313,6 +331,9 @@
                 <td>org.gradle.api.tasks.testing.Test</td>
             </tr>
             <tr>
+                <td>org.gradle.api.tasks.testing.TestReport</td>
+            </tr>
+            <tr>
                 <td>org.gradle.api.tasks.Upload</td>
             </tr>
             <tr>
@@ -324,9 +345,6 @@
             <tr>
                 <td>org.gradle.api.tasks.bundling.Zip</td>
             </tr>
-            <tr>
-                <td>org.gradle.api.plugins.buildcomparison.gradle.CompareGradleBuilds</td>
-            </tr>
         </table>
     </section>
 
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.ResolutionStrategy.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.ResolutionStrategy.xml
index ddde2ac..e5bc1b3 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.ResolutionStrategy.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.ResolutionStrategy.xml
@@ -32,6 +32,9 @@
             <tr>
                 <td>cacheChangingModulesFor</td>
             </tr>
+            <tr>
+                <td>eachDependency</td>
+            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.invocation.Gradle.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.invocation.Gradle.xml
index 7e58105..49a9def 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.invocation.Gradle.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.invocation.Gradle.xml
@@ -14,6 +14,9 @@
                 <td>gradleUserHomeDir</td>
             </tr>
             <tr>
+                <td>gradleHomeDir</td>
+            </tr>
+            <tr>
                 <td>gradle</td>
             </tr>
             <tr>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.DistributionExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.DistributionExtension.xml
new file mode 100644
index 0000000..1ad3d3e
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.DistributionExtension.xml
@@ -0,0 +1,24 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr><td>Name</td>
+                    <td>Default value</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>name</td>
+                <td><literal>project.name</literal></td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr><td>Name</td></tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.Checkstyle.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.Checkstyle.xml
index 3b8c1e0..15150d6 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.Checkstyle.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.Checkstyle.xml
@@ -21,6 +21,10 @@
                 <td><literal>project.checkstyle.ignoreFailures</literal></td>
             </tr>
             <tr>
+                <td>showViolations</td>
+                <td><literal>project.checkstyle.showViolations</literal></td>
+            </tr>
+            <tr>
                 <td>configProperties</td>
                 <td><literal>project.checkstyle.configProperties</literal></td>
             </tr>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CheckstyleExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CheckstyleExtension.xml
index d2f4d8f..79de687 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CheckstyleExtension.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CheckstyleExtension.xml
@@ -29,6 +29,11 @@
                 <td><literal>[:]</literal></td>
                 <td><literal>project.checkstyleProperties</literal></td>
             </tr>
+            <tr>
+                <td>showViolations</td>
+                <td><literal>true</literal></td>
+                <td><literal>true</literal></td>
+            </tr>
         </table>
     </section>
     <section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.IvyModuleDescriptor.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.IvyModuleDescriptor.xml
index ba573a3..fbf89fd 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.IvyModuleDescriptor.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.IvyModuleDescriptor.xml
@@ -5,13 +5,8 @@
             <thead>
                 <tr>
                     <td>Name</td>
-                    <td>Default with <literal>ivy-publish</literal> plugin</td>
                 </tr>
             </thead>
-            <tr>
-                <td>file</td>
-                <td><literal>$buildDir/publications/«publication name»/ivy.xml</literal></td>
-            </tr>
         </table>
     </section>
     <section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor.xml
new file mode 100644
index 0000000..d09f64e
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor.xml
@@ -0,0 +1,24 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr><td>destination</td></tr>
+            <tr><td>descriptor</td></tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.tasks.PublishToIvyRepository.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.tasks.PublishToIvyRepository.xml
new file mode 100644
index 0000000..31d4151
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.tasks.PublishToIvyRepository.xml
@@ -0,0 +1,24 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr><td>publication</td></tr>
+            <tr><td>repository</td></tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenPom.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenPom.xml
new file mode 100644
index 0000000..fbf89fd
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenPom.xml
@@ -0,0 +1,25 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>withXml</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenPublication.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenPublication.xml
new file mode 100644
index 0000000..292a221
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenPublication.xml
@@ -0,0 +1,28 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>pom</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>pom</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.tasks.PublishToMavenRepository.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.tasks.PublishToMavenRepository.xml
new file mode 100644
index 0000000..31d4151
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.tasks.PublishToMavenRepository.xml
@@ -0,0 +1,24 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr><td>publication</td></tr>
+            <tr><td>repository</td></tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.Test.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.Test.xml
index 451b8f3..923e8af 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.Test.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.Test.xml
@@ -17,6 +17,10 @@
                 <td><literal>project.testResultsDir</literal></td>
             </tr>
             <tr>
+                <td>binResultsDir</td>
+                <td><literal><replaceable>project.testResultsDir</replaceable>/binary/<replaceable>task.name</replaceable></literal></td>
+            </tr>
+            <tr>
                 <td>testReportDir</td>
                 <td><literal>project.testReportDir</literal></td>
             </tr>
@@ -34,7 +38,7 @@
             </tr>
             <tr>
                 <td>testReport</td>
-                <td><literal>true</literal> for JUnit, since Gradle 1.3 <literal>false</literal> for TestNG</td>
+                <td><literal>true</literal></td>
             </tr>
             <tr>
                 <td>scanForTestClasses</td>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.TestReport.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.TestReport.xml
new file mode 100644
index 0000000..65b0fe9
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.TestReport.xml
@@ -0,0 +1,31 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>destinationDir</td>
+            </tr>
+            <tr>
+                <td>testResultDirs</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>reportOn</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/plugins.xml b/subprojects/docs/src/docs/dsl/plugins.xml
old mode 100644
new mode 100755
index e0d77b2..476b2a7
--- a/subprojects/docs/src/docs/dsl/plugins.xml
+++ b/subprojects/docs/src/docs/dsl/plugins.xml
@@ -45,6 +45,9 @@
     <plugin id="application" description="Application Plugin">
         <extends targetClass="org.gradle.api.Project" mixinClass="org.gradle.api.plugins.ApplicationPluginConvention"/>
     </plugin>
+    <plugin id="java-library-distribution" description="Java Library Distribution Plugin">
+        <extends targetClass="org.gradle.api.Project" id="distribution" extensionClass="org.gradle.api.plugins.DistributionExtension"/>
+    </plugin>
     <plugin id="signing" description="Signing Plugin">
         <extends targetClass="org.gradle.api.Project" id="signing" extensionClass="org.gradle.plugins.signing.SigningExtension"/>
     </plugin>
@@ -69,4 +72,4 @@
     <plugin id="jdepend" description="JDepend Plugin">
         <extends targetClass="org.gradle.api.Project" id="jdepend" extensionClass="org.gradle.api.plugins.quality.JDependExtension"/>
     </plugin>
-</plugins>
\ No newline at end of file
+</plugins>
diff --git a/subprojects/docs/src/docs/release/content/script.js b/subprojects/docs/src/docs/release/content/script.js
index 9e2fd11..c30e11a 100644
--- a/subprojects/docs/src/docs/release/content/script.js
+++ b/subprojects/docs/src/docs/release/content/script.js
@@ -6,13 +6,11 @@ $(function() {
       rect.top >= 0 &&
       rect.left >= 0 &&
       rect.bottom <= window.innerHeight &&
-      rect.right <= window.innerWidth 
+      rect.right <= window.innerWidth
     );
   }
-  
-  
-  $("section.major-detail").each(function() {
-    var section = $(this);
+
+  function addDetailCollapsing(section) {
     section.hide();
     var buttonParagraph = $("<p><button class='display-toggle'>More »</button></p>").insertAfter(section);
     buttonParagraph.find("button").click(function() {
@@ -24,9 +22,9 @@ $(function() {
           button.text(hiding ? "More »" : "« Less");
         });
       };
-      
+
       var header = section.prevAll("h3:first");
-      
+
       if (hiding && !elementInViewport(header.get(0))) {
         var i = 0;
         $('html,body').animate({
@@ -40,5 +38,100 @@ $(function() {
         toggle();
       }
     });
+  }
+
+  // Do fixed issues
+  var fixedIssues = $("h2#fixed-issues");
+  if (fixedIssues.size() > 0) {
+    $("<p class='loading-issues'>Retrieving the fixed issue information for @versionBase@ …</p>").insertAfter(fixedIssues);
+    var para = $("p.loading-issues")
+    var animate = true;
+    var paraFadeOut = function() {
+      para.fadeOut("80", animate ? paraFadeIn : null);
+    };
+    var paraFadeIn = function() {
+      para.fadeIn("80", animate ? paraFadeOut : null);
+    };
+    var finishAnimation = function() {
+      animate = false;
+      para.remove();
+    };
+    paraFadeOut();
+
+    $.ajax("http://services.gradle.org/fixed-issues/@versionBase@?callback=?", {
+      dataType: "jsonp",
+      cache: true,
+      success: function(data, textStatus, jqXHR) {
+        finishAnimation();
+        var para = $("<p>" + data.length + " issues have been fixed in Gradle @versionBase at .</p>").insertAfter(fixedIssues);
+        if (data.length > 0) {
+          var list = $("<ul id='fixed-issues-list'></ul>").hide().insertAfter(para)
+          $.each(data, function (i, issue) {
+            $("<li>[<a href='" + issue["link"] + "'>"+ issue["key"] + "</a>] - " + issue["summary"] + "</li>").appendTo(list);
+          });
+          list.slideDown("slow");
+        }
+      },
+      timeout: 10000,
+      error: function() {
+        finishAnimation();
+        $("<p>Unable to retrieve the fixed issue information. You may not be connected to the Internet, or there may have been an error.</p>").insertAfter(fixedIssues).css({fontWeight: "bold", color: "red"});
+      }
+    });
+  }
+
+  // Do known issues
+  var knownIssues = $("h2#known-issues");
+  if (knownIssues.size() > 0) {
+    var insertAfter = knownIssues;
+
+    var knownIssuesNext = knownIssues.next();
+    if (knownIssuesNext.is("p")) {
+        insertAfter = knownIssuesNext;
+    }
+    $("<p class='loading-issues'>Retrieving the known issue information for @versionBase@ …</p>").insertAfter(insertAfter);
+    var para = $("p.loading-issues")
+    var animate = true;
+    var paraFadeOut = function() {
+      para.fadeOut("80", animate ? paraFadeIn : null);
+    };
+    var paraFadeIn = function() {
+      para.fadeIn("80", animate ? paraFadeOut : null);
+    };
+    var finishAnimation = function() {
+      animate = false;
+      para.remove();
+    };
+    paraFadeOut();
+
+    $.ajax("http://services.gradle.org/known-issues/@versionBase@?callback=?", {
+      dataType: "jsonp",
+      cache: true,
+      success: function(data, textStatus, jqXHR) {
+        finishAnimation();
+
+        if (data.length > 0) {
+          var para = $("<p>There are " + data.length + " known issues of Gradle @versionBase at .</p>").insertAfter(insertAfter);
+          var list = $("<ul id='fixed-issues-list'></ul>").hide().insertAfter(para)
+          $.each(data, function (i, issue) {
+            $("<li>[<a href='" + issue["link"] + "'>"+ issue["key"] + "</a>] - " + issue["summary"] + "</li>").appendTo(list);
+          });
+          list.slideDown("slow");
+        } else {
+            $("<p>There are no known issues of Gradle @versionBase@ at this time.</p>").insertAfter(insertAfter);
+        }
+      },
+      timeout: 10000,
+      error: function() {
+        finishAnimation();
+        $("<p>Unable to retrieve the known issue information. You may not be connected to the Internet, or there may have been an error.</p>").insertAfter(insertAfter).css({fontWeight: "bold", color: "red"});
+      }
+    });
+  }
+
+  $("section.major-detail").each(function() {
+    addDetailCollapsing($(this));
   });
+
+
 });
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/release/notes-template.md b/subprojects/docs/src/docs/release/notes-template.md
new file mode 100644
index 0000000..18a21d5
--- /dev/null
+++ b/subprojects/docs/src/docs/release/notes-template.md
@@ -0,0 +1,63 @@
+## New and noteworthy
+
+Here are the new features introduced in this Gradle release.
+
+<!--
+### Example new and noteworthy
+-->
+
+## Promoted features
+
+Promoted features are features that were incubating in previous versions of Gradle but are now supported and subject to backwards compatibility.
+See the User guide section on the “[Feature Lifecycle](userguide/feature_lifecycle.html)” for more information.
+
+The following are the features that have been promoted in this Gradle release.
+
+<!--
+### Example promoted
+-->
+
+## Fixed issues
+
+## Incubating features
+
+Incubating features are intended to be used, but not yet guaranteed to be backwards compatible.
+By giving early access to new features, real world feedback can be incorporated into their design.
+See the User guide section on the “[Feature Lifecycle](userguide/feature_lifecycle.html)” for more information.
+
+The following are the new incubating features or changes to existing incubating features in this Gradle release.
+
+<!--
+### Example incubating feature
+-->
+
+## Deprecations
+
+Features that have become superseded or irrelevant due to the natural evolution of Gradle become *deprecated*, and scheduled to be removed
+in the next major Gradle version (Gradle 2.0). See the User guide section on the “[Feature Lifecycle](userguide/feature_lifecycle.html)” for more information.
+
+The following are the newly deprecated items in this Gradle release. If you have concerns about a deprecation, please raise it via the [Gradle Forums](http://forums.gradle.org).
+
+<!--
+### Example deprecation
+-->
+
+## Potential breaking changes
+
+<!--
+### Example breaking change
+-->
+
+## External contributions
+
+We would like to thank the following community members for making contributions to this release of Gradle.
+
+<!--
+* Some Person - fixed some issue (GRADLE-1234)
+-->
+
+We love getting contributions from the Gradle community. For information on contributing, please see [gradle.org/contribute](http://gradle.org/contribute).
+
+## Known issues
+
+Known issues are problems that were discovered post release that are directly related to changes made in this release.
diff --git a/subprojects/docs/src/docs/release/notes.md b/subprojects/docs/src/docs/release/notes.md
index b824371..66d5258 100644
--- a/subprojects/docs/src/docs/release/notes.md
+++ b/subprojects/docs/src/docs/release/notes.md
@@ -1,368 +1,590 @@
-This is a significant release that includes exciting new features and some very important stabilization and optimization, particularly in the area of dependency management.
+Together with the usual set of bug fixes and new features, this release comes with some [major performance improvements](#performance-and-memory-consumption).
+Expect to see faster builds that use less heap space with Gradle 1.4.
 
-Our commitment to the Scala language as a first class citizen in the Gradle ecosystem is evident in this release. Through our hardwork, in cooperation with the folks from 
-[Typesafe](http://typesafe.com/), Gradle now ships with one of the most frequently requested improvements from Scala developers: decreased compile times through 
-[incremental compilation](#incremental-scala-compilation). The ability to [fork compilation](#scala-compilation-in-external-process) has also been added along with 
-improvements to [Eclipse integration for Gradle Scala projects](#improved-scala-ide-integration).
+If you work with Scala and/or Groovy projects, you'll find Gradle more convenient with the auto-detection of [Scala](#automatic-configuration-of-scala-dependency-used-by-scalacompile-and-scaladoc-tasks)
+and [Groovy](#automatic-configuration-of-groovy-dependency-used-by-groovycompile-and-groovydoc-tasks) libraries from the classpath. A Gradle build no longer needs to specifically
+configure a 'groovy' or 'scalaTools' configuration to use these tools.
+Automatic configuration makes it easier to build multiple variants of your software targeting different Scala/Groovy versions, or to use Scala/Groovy only for selected sourceSets.
 
-As software becomes more and more modular and componentized, development teams are requiring more sophistication and more flexibility in how their built items are published for 
-downstream consumption. This release includes the [first step towards a richer and more powerful publication approach](#new-ivy-publishing-mechanism) within Gradle, that provides
-the ability to completely customize the Ivy module descriptor created when publishing in the Ivy format.
+Dependency management continues to be an important focus and this release brings [some significant improvements](#dependency-resolution-improvements),
+[better reporting](#dependencies-that-cannot-be-resolved-are-listed-in-dependency-reports),
+ and a [new, powerful mechanism](#dependency-resolve-rules) providing fine-grained control over dependency resolution.
 
-Dependency management is a critical Gradle function. Many [important fixes/optimizations](#dependency-management-improvements) have been made in this release that improve the general
-dependency management experience in many areas. Along with these improvements, there is an extremely useful [new dependency report](#new-dependencyinsight-report-task) that can be
-used to extract precise information from the resolved dependency graph, with regard to a _particular_ dependency. This report is enabled by the powerful new 
-[Resolution Result API](http://gradle.org/docs/1.2/release-notes#dependency-resolution-result-api) introduced in Gradle 1.2, that has 
-been [further refined in this release](#resolution-result-api). You can expect to see further improvements and innovation in this area in future Gradle versions.
+Keeping Gradle at the forefront of software automation are some exciting new incubating features.
+[Configuration-on-demand](#improved-scalability-via-configuration-on-demand) can reduce startup time for large multi-project builds,
+the [java-library-distribution plugin](#the-new-'java-library-distribution'-plugin) makes it easy to bundle your java project into a distribution,
+and the new [TestReport task](#stand-alone-test-report-task) makes it trivial to aggregate all of your test results into a single report.
 
-As always, you can share your feedback and experiences with Gradle 1.3 via the [Gradle Forums](http://forums.gradle.org). Please read on for detailed information about this release. 
+TestNG users haven't been forgotten, either. They now enjoy the same [high-quality reports](#brand-new-testng-reports-are-generated-by-default) that JUnit users have long had access to.
+
+And that's just the highlights. Read on for more details on why you should upgrade to Gradle 1.4.
+As always, please share your feedback and experiences with Gradle 1.4 via the [Gradle Forums](http://forums.gradle.org).
 
 ## New and noteworthy
 
-Here are the new features introduced in this release.
+### Performance and memory consumption
 
-### New dependencyInsight report task
+Gradle 1.4 introduces some important performance improvements, resulting in faster builds that use less heap space. These improvements affect
+dependency resolution, task up-to-date checks and test execution. In other words, everything that a typical Java based project uses in its build.
 
-The new `dependencyInsight` report complements the `dependencies` report (that was [improved in Gradle 1.2](http://gradle.org/docs/1.2/release-notes#improved-dependency-report)). Where 
-the `dependencies` report provides information on the whole dependency graph, the `dependencyInsight` report focusses on providing information on one (or more) dependencies within 
-the graph.
+A typical Java project might see a 10-20% improvement in build time. For builds with many small projects and many unit tests, in combination with the
+Gradle daemon, we are seeing a 50% improvement in build time.
 
-The report can be used to answer (very common) questions such as:
+#### Faster dependency resolution
 
-* Why is this dependency in the dependency graph?
-* Exactly which dependencies are pulling this dependency into the graph?
-* What is the actual version (i.e. *selected* version) of the dependency that will be used? 
-    * Is it the same as what was *requested*?
-* Why is the *selected* version of a dependency different to the *requested*?
+As mentioned below, resolution of Maven SNAPSHOT versions is now faster, due to fewer network requests and various other internal changes.
 
-The *selected* version of a dependency can be different to the *requested* (i.e. user declared) version due to dependency conflict resolution or by explicit dependency force rules. 
-Similar to the standard gradle depenency report, the `dependencyInsight` report shows both versions. It also shows a requested dynamic version (e.g. “`junit:junit:4.+`”) together 
-with the actually selected version (e.g. “`junit:junit:4.10`”). Please keep in mind that Maven snapshot dependencies are not treated as dynamic versions but as changing modules, 
-similar to what Maven does (for the difference see the [userguide](userguide/dependency_management.html#sec:dependency_management_overview)). Maven snapshots might be treated 
-as dynamic versions in a future version of Gradle which would provide nice insight into pom snapshot resolution.
+Artifact meta-data is now cached more efficiently, making it much faster to read and write to disk. This means that resolution of all
+dependencies can be performed more quickly. In particular, reading of cached meta-data is much faster, so that resolution of dependencies that
+have been cached is much faster.
 
-The `dependencyInsight` report task is invaluable when investigating how and why a dependency is resolved, and it is available in all projects out of the box.
+This in turn means that task up-to-date checks are faster, as typically a large portion of the time to perform an up-to-date check is made up of
+the time it takes to resolve the dependencies that form the task inputs.
 
-#### Example usage
+#### Faster test execution
 
-Given the following build script:
+Test execution typically has a significant effect on build time. While your actual test code won't run any faster in Gradle 1.4, Gradle is now much
+more efficient in managing test execution and generating result and report files.
 
-    apply plugin: 'java'
+Gradle runs tests in a separate worker JVM, to keep the build and tests isolated from each other. The mechanism for controlling these workers and
+dispatching tests to them is now much more efficient, meaning that tests will start to execute in the worker processes more quickly, and that the CPU
+cores can spend more of their time executing tests. This mechanism is now much more robust and can handle a very large number of test classes, fixing a
+number of deadlock conditions.
 
-    repositories {
-        maven { url "http://repo.gradle.org/gradle/repo" }
-    }
+Previously, Gradle collected the test results as XML files and generated the HTML report from these XML files. Test results are now written in an
+efficient binary format, and the XML files and HTML report generated from this. The work of generating the results has moved from the test worker
+processes to the parent build process, meaning that the workers can spend more of their time executing tests, while the results are generated
+asynchronously by the parent. In addition, all result and report file generation streams the content to file, keeping heap usage at a minimum.
 
-    dependencies {
-        compile 'org.gradle:gradle-tooling-api:1.2'
-        compile 'org.slf4j:slf4j-api:1.6.5'
-    }
+### Dependency resolution improvements
+
+As with every release, the dependency resolution engine has been improved with bug fixes and performance optimizations.
+
+These improvements are in addition to the new incubating support for [Dependency Resolve Rules](#dependency-resolve-rules), which give you more control over dependency resolution.
+
+#### Fewer network requests when checking for Maven SNAPSHOT artifact updates (performance)
+
+When checking whether a Maven SNAPSHOT dependency has been updated remotely, fewer network requests are now made to the repository. Previously, multiple 
+requests were made to the `maven-metadata.xml` file where now only one request is made (GRADLE-2585).
+
+This results in faster dependency resolution when using Maven SNAPSHOT dependencies, in particular when importing into an IDE.
+
+#### Faster searching for locally available dependency artifacts (performance)
+
+Before Gradle downloads a dependency artifact from a remote repository, it will selectively search the local file system for that exact same file 
+(i.e. a file with the exact same checksum). For example, Gradle will search the user's “local maven” repository. If the file is found, it will be 
+copied from this location which is much faster than downloading the file (which would be exactly the same) over the network. This is completely 
+transparent and safe.
+
+The algorithm used to search for “local candidates” has been improved and is now faster. This affects all builds using dependency management, especially when building for the first time (GRADLE-2546).
 
-Let's find out about the `org.gradle:gradle-messaging` dependency:
+#### Maven SNAPSHOT artifacts with classifiers are now correctly “changing”
 
-<pre><tt>> gradle dependencyInsight --dependency org.gradle:gradle-messaging
-:dependencyInsight
-org.gradle:gradle-messaging:1.2
-+--- org.gradle:gradle-tooling-api:1.2
-|    \--- compile
-\--- org.gradle:gradle-core:1.2
-     \--- org.gradle:gradle-tooling-api:1.2 (*)
+For dependencies originating in Maven repositories, Gradle follows Maven semantics and treats any dependency artifact whose version number ends 
+in '`-SNAPSHOT`' as “changing”, 
+which means that it can change over time and Gradle should periodically check for updates instead of caching indefinitely 
+(see “[controlling caching](userguide/dependency_management.html#sec:controlling_caching)”). Previously, artifacts with classifiers
+(e.g `sources` or `javadoc`) were not being checked for changes. This has been fixed in this release (GRADLE-2175).
 
-(*) - dependencies omitted (listed previously)</tt>
+#### More robust `--offline` mode
+
+Previously, Gradle discarded cached artifacts just prior to attempting to fetch an updated version from the remote source. If the fetch of the remote 
+artifact failed (e.g. network disruption), there was no longer a cached version available to be used in `--offline` mode.
+This resulted in some situations where trying to use `--offline` mode in response to unexpected network issues would not work well.
+
+Gradle now only discards old artifacts after a newer version has been cached, making `--offline` mode more reliable and useful (GRADLE-2364).
+
+#### Using a “maven” layout with an Ivy repository
+
+By default, an Ivy repository will store the module "`org.my.group:module:version`" with a directory pattern like `baseurl/org.my.group/module`,
+while a maven repository would store the same module under `baseurl/org/my/group/module`.
+It is now possible to configure an `ivy` repository that uses the maven directory layout, [using the new `m2compatible` flag with the `pattern` layout](userguide/dependency_management.html#N145D5).
+
+### Dependencies that cannot be resolved are listed in dependency reports
+
+With earlier versions of Gradle, it was not possible to generate a dependency resolution report if one of the dependencies could not be resolved. This has been rectified.
+Not only will the dependency resolution reports run successfully if a dependency cannot be resolved, the reports will now include details of those dependencies that couldn't be resolved.
+
+Here is an example for the `dependencies` task:
+
+<pre><tt>compile - Classpath for compiling the sources.
+\--- foo:bar:1.0
+     \--- foo:baz:2.0 FAILED
+</tt>
 </pre>
 
-Using this report, it is easy to see that the `gradle-messaging` dependency is being included transitively through `gradle-tooling-api` and `gradle-core` (which itself is a 
-dependency of `gradle-tooling-api`).
-
-Whereas the `dependencies` report shows the path from the top level dependencies down through the transitive dependencies, the `dependencyInsight` report shows the path from 
-a particular dependency to the dependencies that pulled it in. That is, it is an *inverted* view of the `dependencies` report.
-
-The `dependencyInsight` report is also useful for understanding the difference between *requested* and *selected* versions.  
-
-<pre><tt>> gradle dependencyInsight --dependency slf4j --configuration runtime
-:dependencyInsight
-org.slf4j:slf4j-api:1.6.6 (conflict resolution)
-+--- org.gradle:gradle-tooling-api:1.2
-|    \--- runtime
-+--- org.gradle:gradle-messaging:1.2
-|    +--- org.gradle:gradle-tooling-api:1.2 (*)
-|    \--- org.gradle:gradle-core:1.2
-|         \--- org.gradle:gradle-tooling-api:1.2 (*)
-+--- org.gradle:gradle-base-services:1.2
-|    +--- org.gradle:gradle-tooling-api:1.2 (*)
-|    +--- org.gradle:gradle-messaging:1.2 (*)
-|    \--- org.gradle:gradle-core:1.2 (*)
-\--- org.gradle:gradle-core:1.2 (*)
-
-org.slf4j:slf4j-api:1.6.5 -> 1.6.6
-\--- runtime
-
-(*) - dependencies omitted (listed previously)</tt>
+The `FAILED` marker indicates that `foo:baz:2.0`, which is depended upon by `foo:bar:1.0`, couldn't be resolved.
+
+A similar improvement has been made to the `dependencyInsight` task:
+
+<pre><tt>foo:baz:2.0 (forced) FAILED
+
+foo:baz:1.0 -> 2.0 FAILED
+\--- foo:bar:1.0
+     \--- compile
+</tt>
 </pre>
 
-Here we see that while `slf4j-api:1.6.5` was *requested*, `slf4j-api:1.6.6` was *selected* due to the conflict resolution. We can also see which dependency pulled in the slf4j.
+In this example, `foo:baz` was forced to version `2.0`, and that version couldn't be resolved.
 
-In this case, we were interested in the `runtime` dependency configuration. The default configuration the report uses is `compile`.
+### Filter dependency resolution reports by configuration
 
-For more information, see the [DependencyInsightReportTask documentation](dsl/org.gradle.api.tasks.diagnostics.DependencyInsightReportTask.html).
+The `dependencies` task now accepts an optional `--configuration` parameter that restricts its output to a particular configuration:
 
-### Incremental Scala compilation
+    $ gradle dependencies --configuration compile
 
-Due to the sophistication of the language and type system, Scala programs can take a long time to compile. Gradle 1.3 addresses this problem by
-integrating with [Zinc](https://github.com/typesafehub/zinc), a standalone version of [sbt](https://github.com/harrah/xsbt)'s incremental Scala
-compiler. By compiling only classes whose source code has changed since the previous compilation, and classes affected by these changes,
-Zinc can significantly reduce Scala compilation time. It is particularly effective when frequently compiling small code
-increments, as is often done at development time.
+This command will display the dependency tree rooted at the `compile` configuration, and (assuming a standard
+Java project) omit the dependency trees for the `runtime`, `testCompile`, and `testRuntime` configurations.
 
-To switch the `ScalaCompile` task from the default Ant based compiler to the new Zinc based compiler, use `scalaCompileOptions.useAnt = false`. 
-Except where noted in the [API documentation](dsl/org.gradle.api.tasks.scala.ScalaCompile.html), the Zinc based
-compiler supports exactly the same configuration options as the Ant based compiler.
+### Automatic configuration of Groovy dependency used by `GroovyCompile` and `Groovydoc` tasks
 
-Just like the Ant based compiler, the Zinc based compiler supports joint compilation of Java and Scala code. By default, all Java and Scala code
-under `src/main/scala` will participate in joint compilation. With the Zinc based compiler, even Java code will be compiled incrementally.
+The `groovy-base` plugin now automatically detects the Groovy dependency used on the compile class path of any `GroovyCompile` or `Groovydoc` task,
+and appropriately configures the task's `groovyClasspath`.
+As a consequence, the Groovy dependency can now be configured directly for the configuration(s) that need it, and it is no longer necessary
+to use the `groovy` configuration.
 
-To learn more about incremental Scala compilation, see the [Scala plugin](userguide/scala_plugin.html#N12A97) chapter in the Gradle User Guide.
+Old (and still supported):
 
-### Scala compilation in external process
+    dependencies {
+        groovy "org.codehaus.groovy:groovy-all:2.0.5"
+    }
 
-Scala compilation can now be performed outside the Gradle JVM in a dedicated compiler process, which can help to deal with memory issues. 
+New (and now preferred):
 
-External compilation is supported both for the existing Ant-based and the new Zinc-based Scala compiler. The API is very similar to that for Java and
-Groovy: [`ScalaCompile.fork = true`](dsl/org.gradle.api.tasks.scala.ScalaCompile.html#org.gradle.api.tasks.scala.ScalaCompile:fork)
-activates external compilation, and [`ScalaCompile.forkOptions`](dsl/org.gradle.api.tasks.scala.ScalaCompile.html#org.gradle.api.tasks.scala.ScalaCompile:forkOptions)
-allows to adjust memory settings.
+    dependencies {
+        compile "org.codehaus.groovy:groovy-all:2.0.5"
+    }
+
+Automatic configuration makes it easier to build multiple artifact variants targeting different Groovy versions, or to only use Groovy
+for selected source sets:
+
+    dependencies {
+        testCompile "org.codehaus.groovy:groovy-all:2.0.5"
+    }
+
+Apart from the `groovy-all` Jar, Gradle also detects usages of the `groovy` Jar and `-indy` variants. Automatic configuration is disabled
+if a task's `groovyClasspath` is non-empty (for example because the `groovy` configuration is used) or no repositories are declared
+in the project.
+
+### Automatic configuration of Scala dependency used by `ScalaCompile` and `Scaladoc` tasks
 
-### Improved Scala IDE integration
+The `scala-base` plugin now automatically detects the `scala-library` dependency used on the compile class path of any `ScalaCompile` or `ScalaDoc` task,
+and appropriately configures for the task's `scalaClasspath`.
+As a consequence, it is no longer necessary to use the `scalaTools` configuration.
 
-The [Eclipse Plugin](userguide/eclipse_plugin.html) now automatically excludes dependencies already provided by the
- 'Scala Library' class path container. This improvement is essential for [Scala IDE](http://scala-ide.org) to work correctly. It also takes effect
- when using the [Eclipse Gradle Integration](https://github.com/SpringSource/eclipse-integration-gradle).
+Old (and still supported):
 
-### Dependency management improvements
+    dependencies {
+        scalaTools "org.scala-lang:scala-compiler:2.9.2"
+        compile "org.scala-lang:scala-library:2.9.2"
+    }
 
-With this release of Gradle, we have continued to improve our dependency management implementation. The focus for these improvements in Gradle 1.3 is on
-stability and this release includes a number of important fixes.
+New (and now preferred):
 
-#### Artifact cache stability
+    dependencies {
+        compile "org.scala-lang:scala-library:2.9.2"
+    }
 
-This release fixes a number of issues that resulted in corruption of the artifact cache. In particular, Gradle will be much more stable in the case
-where you have many concurrent builds running on a machine, such as a CI build server, and these builds have dependencies that change frequently, such
-as Maven snapshots, or are using a very short cache expiry time.
+Automatic configuration makes it easier to build multiple artifact variants targeting different Scala versions. Here is one way to do it:
+
+    apply plugin: "scala-base"
+
+    sourceSets {
+        scala2_8
+        scala2_9
+        scala2_10
+    }
 
-* [GRADLE-2544](http://issues.gradle.org/browse/GRADLE-2544) - Potential corruption of artifact cache on concurrent access.
-* [GRADLE-2458](http://issues.gradle.org/browse/GRADLE-2458) - Failures to store meta-data are silently ignored.
-* [GRADLE-2457](http://issues.gradle.org/browse/GRADLE-2457) - Potential corruption of artifact cache after a crash.
+    sourceSets.all { sourceSet ->
+        scala.srcDirs = ["src/main/scala"]
+        resources.srcDirs = ["src/main/resources"]
 
-#### Smarter handling of missing modules
+        def jarTask = task(sourceSet.getTaskName(null, "jar"), type: Jar) {
+            baseName = sourceSet.name
+            from sourceSet.output
+        }
+
+        artifacts {
+            archives jarTask
+        }
+    }
+
+    repositories {
+        mavenCentral()
+    }
+
+    dependencies {
+        scala2_8Compile "org.scala-lang:scala-library:2.8.2"
+        scala2_9Compile "org.scala-lang:scala-library:2.9.2"
+        scala2_10Compile "org.scala-lang:scala-library:2.10.0-RC5"
+    }
+
+Note that we didn't have to declare the different `scala-compiler` dependencies, nor did we have to assign them
+to the corresponding `ScalaCompile` and `ScalaDoc` tasks. Nevertheless, running `gradle assemble` produces:
+
+<pre><tt>$ ls build/libs
+scala2_10.jar scala2_8.jar  scala2_9.jar
+</tt>
+</pre>
 
-Gradle caches the fact that a given module is missing from a given repository. It uses this information to avoid unnecessary network requests when you
-are using multiple repositories, and when resolving dynamic versions. Previous versions of Gradle were overly keen in using this information, leading
-to problems where a misconfiguration would cause Gradle to decide that a certain module is missing and will be missing forever. In this release, Gradle
-uses a more sensible strategy to decide when to verify that something that it considers to be missing is, in fact, missing.
+With build variants becoming a first-class Gradle feature, building multiple artifact variants targeting different
+Scala versions will only get easier.
 
-* [GRADLE-2455](http://issues.gradle.org/browse/GRADLE-2455) - Smarter handling of missing module versions.
+Automatic configuration isn't used if a task's `scalaClasspath` is non-empty (for example because the `scalaTools`
+configuration is used) or no repositories are declared in the project.
 
-#### Ivy latest status improvements
+### Brand new TestNG reports are generated by default
 
-Previous Gradle releases had a number of issues resolving dynamic versions such as 'latest.integration' and we recently introduced some regressions in
-this area as we've attempted to fix these issues. We've reworked the implementation to simplify it internally and added many more integration tests,
-to avoid further regressions in the future.
+Gradle 1.3 introduced several incubating improvements to TestNG reports.
+In Gradle 1.4 the improved reports are turned on by default.
+The TestNG users will be delighted to learn that:
 
-* [GRADLE-2502](http://issues.gradle.org/browse/GRADLE-2502) - Latest status resolution broken in Gradle 1.1.
-* [GRADLE-2504](http://issues.gradle.org/browse/GRADLE-2504) - NPE resolving dynamic versions from multiple repositories.
+* The new HTML report is much easier to read and browse than the standard TestNG report. It's also quite pretty.
+* Both reports, XML (for your CI server) and HTML (for you), contain the test output (i.e. messages logged to the standard streams or via the standard logging toolkits).
+This is extremely useful for debugging certain test failures.
+* The reports neatly work with TestNG and Gradle parallel testing ([test.maxParallelForks](dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:maxParallelForks)).
 
-#### Other fixes
+The implementation of the new reports is now a part of Gradle.
+Previously, the report generation was delegated to TestNG's default listeners that are shipped with TestNG library.
+You can switch off the HTML report generation by configuring the [test.testReport](dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:testReport) property.
+If you prefer the old TestNG reports please refer to the [documentation](groovydoc/org/gradle/api/tasks/testing/testng/TestNGOptions.html#useDefaultListeners).
 
-This release includes a number of other useful fixes:
+### Easier embedding of Gradle via Tooling API
 
-* [GRADLE-2547](http://issues.gradle.org/browse/GRADLE-2547) - Temporary files are left in $GRADLE_HOME/caches/artifacts-*/filestore.
-* [GRADLE-2543](http://issues.gradle.org/browse/GRADLE-2543) - Performance regression in local repository access.
-* [GRADLE-2486](http://issues.gradle.org/browse/GRADLE-2486) - Dependency resolution failures when running in parallel execution mode.
+The [Tooling API](userguide/embedding.html), the standard way to embed Gradle, is now more convenient to use.
+As of Gradle 1.4 it ships as a single jar with the only external dependency being an SLF4J implementation.
+All other dependencies are packaged inside the Jar and shaded to avoid conflicts with the embedder's classpath.
 
+<!--
 ## Promoted features
 
-Promoted features are features that were incubating in previous versions of Gradle but are now supported and subject to the backwards compatibility policy.
+Promoted features are features that were incubating in previous versions of Gradle but are now supported and subject to backwards compatibility.
+See the User guide section on the “[Feature Lifecycle](userguide/feature_lifecycle.html)” for more information.
 
-### Improved test logging APIs
+The following are the features that have been promoted in this Gradle release.
 
-[Gradle 1.1 introduced much improved test logging output](http://gradle.org/docs/1.1/release-notes#test-logging). The APIs that provided the ability to configure this logging
-were introduced in the incubating state. In this Gradle release, these APIs are being promoted and are no longer subject to change without notice.
 
-For more information on the test logging functionality, see [this forum post](http://forums.gradle.org/gradle/topics/whats_new_in_gradle_1_1_test_logging) that introduced the feature.
+### Example promoted
+-->
 
 ## Fixed issues
 
-The list of issues fixed between 1.2 and 1.3 can be found [here](http://issues.gradle.org/sr/jira.issueviews:searchrequest-printable/temp/SearchRequest.html?jqlQuery=fixVersion+in+%28%221.3-rc-1%22%2C+%221.3-rc-2%22%29+and+resolution+is+not+null+ORDER+BY+priority&tempMax=1000).
-
 ## Incubating features
 
-New features as typically introduced as “_incubating_”. The key characteristic of an incubating feature is that it may change in an incompatible way in a future Gradle release.
-At some time in the future, after incorporating invaluable real-world feedback from Gradle users, the feature will be deemed stable and “_promoted_”. At this point it is no longer subject to
-incompatible change. That is, the feature is now subject to the backwards compatibility policy.
+Incubating features are intended to be used, but not yet guaranteed to be backwards compatible.
+By giving early access to new features, real world feedback can be incorporated into their design.
+See the User guide section on the “[Feature Lifecycle](userguide/feature_lifecycle.html)” for more information.
+
+The following are the new incubating features or changes to existing incubating features in this Gradle release.
+
+### Dependency resolve rules
+
+A “dependency resolve rule” is a user specified algorithm that can influence the resolution of a particular dependency.
+Dependency resolve rules can be used to solve many challenging dependency management problems.
+
+For example, a dependency resolve rule can be used to force all versions with a particular “group” to be of the same version:
+
+    configurations.all {
+        resolutionStrategy.eachDependency { DependencyResolveDetails details ->
+            if (details.requested.group == 'org.gradle') {
+                details.useVersion '1.4'
+            }
+        }
+    }
+
+The rule (i.e. the closure given to the `eachDependency` method above) is called for each dependency that is to be resolved. The 
+[`DependencyResolveDetails`](javadoc/org/gradle/api/artifacts/DependencyResolveDetails.html)
+object passed to the rule implementation represents the originally _requested_ and the finally _selected_ version (after conflict resolution
+has been applied). The rule can make a programmatic choice to change how the dependency should be resolved.
+
+This is an “incubating” feature. In Gradle 1.4, it is only possible to affect the version of the dependency that will be resolved. In future versions,
+more control will be allowed via the `DependencyResolveDetails` type.
+
+Dependency resolve rules are a powerful feature that allow you to do much more than just enforcing a certain version of a dependency in advance
+(which [you can also do](dsl/org.gradle.api.artifacts.ResolutionStrategy.html) with Gradle).
+Many interesting use cases can be implemented with the dependency resolve rules:
+
+* [Blacklisting a version](userguide/dependency_management.html#sec:blacklisting_version) with a replacement
+* Implementing a [custom versioning scheme](userguide/dependency_management.html#sec:custom_versioning_scheme)
+* [Modelling a releasable unit](userguide/dependency_management.html#sec:releasable_unit) - a set of related libraries that require a consistent version
+
+For more information, including more code samples, please refer to this [user guide section](userguide/dependency_management.html#sec:dependency_resolve_rules).
+
+### Improved scalability via configuration on demand
+
+In Gradle, all projects are configured before any task gets executed (see [the build lifecycle](userguide/build_lifecycle.html#sec:build_phases)).
+Huge multi-project builds may have a noticeable configuration time for that reason.
+To improve the experience of working with large multi-project builds "configuration on demand" mode is introduced, where only those projects required
+by the build are configured. This mode is incubating and currently it is not guaranteed to work with every multi-project build.
+It should work very well with builds that have [decoupled projects](userguide/multi_project_builds.html#sec:decoupled_projects)
+(e.g. avoiding having a subproject accessing the model of another project).
+Before you start configuring on demand, please read the section in the [user guide](userguide/multi_project_builds.html#sec:configuration_on_demand).
+Then update your gradle.properties file:
+
+    #gradle.properties file
+    systemProp.org.gradle.configuration.ondemand=true
+
+### The new 'java-library-distribution' plugin
+
+The new incubating '[`java-library-distribution`](userguide/javaLibraryDistribution_plugin.html)' plugin, contributed by Sébastien Cogneau, makes it is much easier to create a
+standalone distribution for a JVM library.
+
+Let's walk through a small example. Assume a project with the following layout:
+
+<pre><tt>MyLibrary
+|____build.gradle
+|____libs // a directory containing third party libraries
+| |____a.jar
+|____src
+| |____main
+| | |____java
+| | | |____SomeLibrary.java
+| |____dist // additional files that should go into the distribution
+| | |____dir2
+| | | |____file2.txt
+| | |____file1.txt
+</tt>
+</pre>
+
+In the past, it was necessary to declare a custom `zip` task that assembles the distribution. Now, the 'java-library-distribution' will do the job for you:
+
+    apply plugin: 'java-library-distribution'
+
+    dependencies {
+        runtime files('libs/a.jar')
+    }
+
+    distribution {
+        name = 'MyLibraryDistribution'
+    }
+
+Given this configuration, running `gradle distZip` will produce a zip file named `MyLibraryDistribution.zip` that contains the library itself,
+its runtime dependencies, and everything in the `src/dist` directory.
+
+To add further files to the distribution, configure the `distZip` task accordingly:
+
+    distZip {
+        from('aFile')
+        from('anotherFile') {
+            into('dist')
+        }
+    }
+
+### Stand-alone test report task
+
+A new, incubating `TestReport` task type is now available. This task takes the test results generated by one or more `Test` tasks and generates
+a combined HTML test report from them. For example, you can use this task to generate a single test report for all the projects in the build:
+
+    task testReport(type: TestReport) {
+        destinationDir = file("$buildDir/reports/all-tests")
+        reportOn subprojects*.test
+    }
+
+The test report task currently combines test results, but does not aggregate the test results for a given class. So, if a given class is run by
+multiple `Test` tasks, only one execution of the class will be included in the report and the other executions of that class
+will be discarded. This will be addressed in a later Gradle version.
+
+For more details, see the [user guide](userguide/java_plugin.html#test_reporting)
+
+### Generate `ivy.xml` without publishing
+
+The incubating '`ivy-publish`' plugin introduces a new `GenerateIvyDescriptor` task generates the Ivy metadata file (a.k.a. `ivy.xml`) for publication.
+The task name for the default Ivy publication is '`generateIvyModuleDescriptor`'.
+
+This function used to be performed internally by the `PublishToIvyRepository` task. By having this function be performed by a separate task
+you can generate the `ivy.xml` metadata file without having to publish your module to an Ivy repository, which makes it easier to test/check
+the descriptor. 
+
+The `GenerateIvyDescriptor` task also allows the location of the generated Ivy descriptor file to changed from its default location at ‘`build/publications/ivy/ivy.xml`’.
+This is done by setting the `destination` property of the task:
+
+    apply plugin: 'ivy-publish'
 
-Typically, the implementation quality of incubating features is sound. For some very challenging engineering problems, such as the Gradle Daemon or parallel task execution, it is 
-impossible to get the implementation quality right from the beginning as the feature needs real world exposure. The feedback from the incubation phase can be used to iteratively 
-improve the stability of the feature.
+    group = 'group'
+    version = '1.0'
 
-By releasing features early in the incubating state, users gain a competitive advantage through early access to new functionality in exchange for helping refine it over time, 
-ultimately making Gradle a better tool.
+    // … declare dependencies and other config on how to build
+
+    generateIvyModuleDescriptor {
+        destination = 'generated-ivy.xml'
+    }
 
-To learn more, see the [User Guide chapter on the Feature Lifecycle](userguide/feature_lifecycle.html).
+Executing `gradle generateIvyModuleDescriptor` will result in the Ivy module descriptor being written to the file specified. This task is automatically wired
+into the respective `PublishToIvyRepository` tasks, so you do not need to explicitly call this task to publish your module.
 
-### New Ivy publishing mechanism
+### The new ‘maven-publish’ plugin
 
-This release introduces a new mechanism for publishing to Ivy repositories in the Ivy format. It also introduces some new general publishing constructs. This new publishing support is *incubating* and will co-exist with the [existing methods for publication](userguide/artifact_management.html) until the time where it supersedes it, at which point the old mechanism will 
-become deprecated. The functionality included in this release is the first step down the path of providing a better solution for sharing the artifacts built in your Gradle builds.
+The new incubating ‘maven-publish’ plugin is an alternative to the existing ‘maven’ plugin, and will eventually replace it. This plugin builds on the new publishing model
+that was introduced in Gradle 1.3 with the ‘ivy-publish’ plugin. The new publication mechanism (which is currently “incubating”, including this plugin) will
+expand and improve over the subsequent Gradle releases to provide more convenience and flexibility than the existing publication mechanism plus very powerful features
+to wire your components across builds & teams.
 
-In this release, we have focussed on laying the groundwork and providing the ability to modify the Ivy module descriptor that is published during a publish operation. It has long been possible
-to fully customise the `pom.xml` when publishing in the Maven format; it is now possible to do the same when publishing in the Ivy format.
+In the simplest case, publishing to a Maven repository looks like:
 
-#### The new 'ivy-publish' plugin
+    apply plugin: 'java'
+    apply plugin: 'maven-publish'
 
-The new functionality is provided by the '`ivy-publish`' plugin. In the simplest case, publishing using this plugin looks like…
+    group = 'group'
+    version = '1.0'
 
-    apply plugin: "ivy-publish"
-    
     // … declare dependencies and other config on how to build
-    
+
     publishing {
         repositories {
-            ivy {
-                url "http://mycompany.org/repo"
+            maven {
+                url 'http://mycompany.org/repo'
             }
         }
     }
 
-To publish, you simply run the “`publish`” task.
+To publish, you simply run the `publish` task. The POM file will be generated and the main artifact uploaded to the declared repository.
+To publish to your local Maven repository (ie 'mvn install') simply run the `publishToMavenLocal` task. You do not need to have `mavenLocal` in your
+`publishing.repositories` section.
 
-To modify the descriptor, you use a programmatic hook that modifies the descriptor content as XML. This is the same approach that you take in Gradle when modifying IDE metadata XML or Maven POM XML content.
+To modify the generated POM file, you can use a programmatic hook that modifies the descriptor content as XML.
 
-    publishing {
-        publications {
-            ivy {
-                descriptor {
-                    withXml {
-                        asNode().dependencies.dependency.find { it. at org == "junit" }. at rev = "4.10"
-                    }
-                }
+    publications {
+        maven {
+            pom.withXml {
+                asNode().appendNode('description', 'A demonstration of maven POM customisation')
             }
         }
     }
 
-In this example we are modifying the version that is expressed of our `junit` dependency. With this hook, you can modify any aspect of the descriptor. You could for example easily build a functionality similar to Ivy deliver on top of this in conjunction with the new <a href="#resolution-result-api">Resolution Result API</a>. In general, it can be useful to optimize the descriptor for consumption instead of having it be an accurate record of how the module was built. 
+In this example we are adding a ‘`description`’ element for the generated POM. With this hook, you can modify any aspect of the POM.
+For example, you could replace the version range for a dependency with the actual version used to produce the build.
+This allows the POM file to describe how the module should be consumed, rather than be a description of how the module was built.
 
-For more information on the new publishing mechanism, see the [new User Guide chapter](userguide/publishing_ivy.html).
+For more information on the new publishing mechanism, see the [new User Guide chapter](userguide/publishing_maven.html).
 
-### Improved TestNG HTML report
+## Deprecations
 
-Gradle has long shipped with HTML report functionality for JUnit test results that improves on the Ant default. It is now possible to use the same HTML report format for TestNG test results.
-See the [reports generated by the Gradle automated builds](http://builds.gradle.org/repository/download/bt9/.lastSuccessful/reports/ide/integTest/index.html) as an example of the new improved report.
+### Changing certain task configuration during and after execution
 
-The reports are not yet turned on by default. To enable the new TestNG test reports, simply set 
-[testReport = true](dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:testReport) on your test task.
+Much of a task's configuration influences how, or even if, a task should execute. After the task has executed, changing the configuration has no useful effect.
+For example, it does not make sense to add an action via the `doFirst()` method to a task that is executing or has already executed.
+Changing such configuration has been deprecated and this will become an error condition in Gradle 2.0.
 
-Note: The new report might exhibit increased heap usage for tests that log many messages to the standard streams. You can increase the heap allocated to the test process via the 
-[jvmArgs property](dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:jvmArgs) of the test task.
+#### Changing the action list
 
-#### Improvement details
+Once a task has started executing, its action list should no longer be changed. This includes calling the following methods on `Task` objects:
 
-* The html report is easier to read and browse than the standard TestNG report
-* Both reports, xml (for CI) and html (for you), contain the test output (i.e. messages logged to the standard streams or via the standard logging toolkits). This is extremely useful for debugging certain test failures.
-* The reports neatly work with Gradle parallel testing ([test.maxParallelForks](dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:maxParallelForks)) and forking features ([test.forkEvery](dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:forkEvery)). The standard TestNG reports are not compatible with Gradle's parallel test execution and can contain confusing or incorrect information.
+* `setActions()`
+* `doLast()` - including using the synonymous `<<` operator
+* `doFirst()`
 
-## Deprecations
+Mutating the collection returned by `getActions()` is also deprecated after the task has started executing.
 
-### Ant-task based Java compiler integration
+#### Changing task dependencies 
 
-Gradle currently supports two different Java compiler integrations: A native Gradle integration that uses the compiler APIs directly, and an Ant-task
-based implementation that uses the `<javac>` Ant task. The native Gradle integration has been the default since Gradle 1.0-milestone-9. Some of its advantages are:
+Once a task has started executing, its dependencies should no longer be changed. This includes calling the following methods on `Task` objects:
 
-* Faster compilation
-* Can run in Gradle compiler daemon (external process that is reused between tasks)
-* More amenable to future enhancements
+* `dependsOn()`
+* `setDependsOn()`
 
-The Ant-task based integration has now been deprecated and will be removed in Gradle 2.0. As a result, the following properties of `CompileOptions` are also
-deprecated and will be removed in Gradle 2.0:
+#### Changing execution conditions
 
-* `useAnt`
-* `optimize`
-* `includeJavaRuntime`
+Once a task has started executing, its configuration controlling whether it will be execution should no longer be changed. 
+This includes calling the following methods on `Task` objects:
 
-### Ant-task based Groovy compiler integration
+* `onlyIf()`
+* `setOnlyIf()`
+* `setEnabled()`
 
-Gradle currently supports two different Groovy compiler integrations: A native Gradle integration that uses the compiler APIs directly, and an Ant-task
-based implementation that uses the `<groovyc>` Ant task. The native Gradle integration has been the default since Gradle 1.0. Some of its advantages are:
+#### Changing task inputs
 
-* Faster compilation
-* Correct handling of AST transformations
-* Can run in Gradle compiler daemon (external process that is reused between tasks)
-* More amenable to future enhancements
+Once a task has started executing, its “inputs” configuration should no longer be changed. 
+This includes calling the following methods on `TaskInputs` objects:
 
-The Ant-task based integration has now been deprecated and will be removed in Gradle 2.0. As a result, the following properties of `GroovyCompileOptions` are also
-deprecated and will be removed in Gradle 2.0:
+* `files()`
+* `file()`
+* `dir()`
+* `property()`
+* `properties()`
+* `source()`
+* `sourceDir()`
 
-* `useAnt`
-* `stacktrace`
-* `includeJavaRuntime`
+#### Changing task outputs
 
-### Changing the name of a repository once added to a repository container
+Once a task has started executing, its “outputs” configuration should no longer be changed. 
+This includes calling the following methods on `TaskOutputs` objects:
 
-The [`ArtifactRepository`](javadoc/org/gradle/api/artifacts/repositories/ArtifactRepository.html) type has a `setName(String)` method that you
-could use to change the repository name after it has been created. Doing so has been deprecated. The name of the repository should be specified at creation time via the DSL.
+* `upToDateWhen()`
+* `files()`
+* `file()`
+* `dir()`
 
-For example:
+## Potential breaking changes
 
-    repositories {
-        ivy {
-            name "my-ivy-repo"
-        }
-    }
-    
-    // The following is now deprecated
-    repositories.ivy.name = "changed-name"
+### `DependencyReportTask` and `DependencyInsightReportTask` no longer fail when dependencies cannot be resolved
 
-A deprecation warning will be issued if a name change is attempted. It has been deprecated because changing the name of a repository after it has been added to the 
-container can cause problems when tasks are automatically created for created repositories.
+Previously, these tasks types would fail if one or more dependencies could not be resolved. Now, they no longer fail and instead display the failed
+dependencies in the appropriate place in the output.
 
-## Changes to existing incubating features
+### `DependencyInsightReportTask` throws better exception on bad configuration
 
-### Resolution result API
+Previously, when the task's configuration was invalid a `ReportException` would be thrown when the task started to execute. For consistency with other tasks, it
+now throws a `InvalidUserDataException`.
 
-The entry point to the ResolutionResult API has changed. You now access the ResolutionResult via [configuration.incoming.resolutionResult](dsl/org.gradle.api.artifacts.Configuration.html#org.gradle.api.artifacts.Configuration:incoming).
-New convenience methods were also added to the API. For more information please refer to the javadoc for [ResolutionResult](javadoc/org/gradle/api/artifacts/result/ResolutionResult.html).
+### Copying a `Configuration` also copies its resolution strategy
 
-### Incubating C++ `Compile` task type removed
+Previously, a copied `Configuration` object shared the same `resolutionStrategy` object as the `Configuration` that it was copied from. This meant that changes 
+to `resolutionStrategy` of the source or the copy effected both instances and resulted in undesirable side affects. Copying a `Configuration` now also creates a
+discrete copy of the `resolutionStrategy`.
 
-This was replaced by `CppCompile` in Gradle 1.2. You should use the replacement class instead.
+### Removed `Jvm.getSupportsAppleScript()`
 
-### Incubating `GppCompileSpec` properties removed
+In the deprecated internal class `org.gradle.util.Jvm` the method `getSupportsAppleScript()` has been removed.
 
-The deprecated `task` property was removed from `GppCompileSpec`.
+If you need to check if the running JVM supports AppleScript, you can use the following code:
 
-## Potential breaking changes
+    import javax.script.ScriptEngine
+    import javax.script.ScriptEngineManager
+
+    ScriptEngineManager mgr = new ScriptEngineManager();
+    ScriptEngine engine = mgr.getEngineByName("AppleScript");
+    boolean isAppleScriptAvailable = engine != null;
 
-### The behavior of [Test.testReport](dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:testReport) property for TestNG
+### Changes to new Ivy publishing support
 
-The default value of the [testReport property value of Test tasks](dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:testReport) has been changed to `false` for TestNG. Previously, this property was ignored when running with TestNG - the html results were generated regardless.
+Breaking changes have been made to the new, incubating, Ivy publishing support.
 
-This property now enables/disables the new [improved TestNG report introduced in this release](#improved-testng-html-report).
+Previously, it was possible to set the `descriptorFile` property on an IvyPublication object. This property has been removed with the introduction of the new
+`GenerateIvyDescriptor` task. To specify where the `ivy.xml` file should be generated, set the `destination` property of the `GenerateIvyDescriptor` task.
 
-### Removed GraphvizReportRenderer
+Previously _all_ configurations of the project were published. Now, only the ‘`archives`’ configuration together with the ‘`default`’ configuration and its 
+ancestors will be published. In practice, this means that a Java project's `testCompile` and `testRuntime` configurations will no longer be published by default.
 
-This was an early contribution that did not work and was an undocumented private type.
+### Changed default value for `TestNGOptions.useDefaultListeners`
 
-### Removed `org.gradle.api.publication` package
+The default value for [TestNGOptions.useDefaultListeners](groovydoc/org/gradle/api/tasks/testing/testng/TestNGOptions.html#useDefaultListeners) has changed from `true` to `false`
+so that Gradle can take over generation of the reports.
+This way Gradle can provide invaluable improvements to the reporting - for more information read the earlier section on TestNG reports.
 
-This package contained some early experiments in a new publication model. It was incomplete and undocumented. It is superseded by the new [publishing functionality introduced in this release](#new-ivy-publishing-mechanism), so has been removed.
+### Updated default versions of Checkstyle and CodeNarc
 
-## Community contributions
+The default version of Checkstyle used for the '`checkstyle`' plugin has been updated from `5.5` to `5.6`.
+
+The default version of CodeNarc used for the '`codenarc`' plugin has been updated from `0.16.1` to `0.18`.
+
+### `eclipseWtpComponent` task overrides dependent modules
+
+Previously, the `eclipse-wtp` plugin's `eclipseWtpComponent` task would add generated `dependent-module` entries to those already contained in the
+`.settings/org.eclipse.wst.common.component` file. This could lead to stale and duplicated entries (see `GRADLE-2526`). Now, existing
+entries are overridden with generated entries, just like it's done for `classpathentry` elements in `.classpath` files.
+
+## External contributions
 
 We would like to thank the following community members for making contributions to this release of Gradle.
 
-* Matt Khan - fixed resetting of `test.debug` to `false` when using `test.jvmArgs`  (GRADLE-2485)
-* Gerd Aschemann - fixes for `application` plugins generated shell scripts (GRADLE-2501)
-* Cruz Fernandez - fixes to the properties sample project
-* Fadeev Alexandr - fixes for Gradle Daemon on Win 7 when `PATH` env var is not set (GRADLE-2461)
-* Ben Manes - improved Scala IDE integration ([pull request #99](https://github.com/gradle/gradle/pull/99))
+* Sébastien Cogneau - Contributing the `java-library-distribution` plugin
+* James Bengeyfield - `showViolations` flag for `Checkstyle` task (GRADLE-1656)
+* Dalibor Novak - `m2compatible` flag on `PatternRepositoryLayout` (GRADLE-1919)
+* Brian Roberts, Tom Denley - Support multi-line JUnit test names (for better ScalaTest compatibility) (GRADLE-2572)
+* Sean Gillespie - Extend the application plugin to build a tar distribution
+
+We love getting contributions from the Gradle community. For information on contributing, please see [gradle.org/contribute](http://gradle.org/contribute).
+
+## Known issues
 
-We love getting contributions from the Gradle community. For information on contributing, please see [gradle.org/contribute](http://gradle.org/contribute).
\ No newline at end of file
+Known issues are problems that were discovered post release that are directly related to changes made in this release.
diff --git a/subprojects/docs/src/docs/userguide/antlrPlugin.xml b/subprojects/docs/src/docs/userguide/antlrPlugin.xml
index 7677233..42f64bd 100644
--- a/subprojects/docs/src/docs/userguide/antlrPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/antlrPlugin.xml
@@ -15,24 +15,30 @@
   -->
 
 <chapter id="antlr_plugin">
-    <title>The Antlr Plugin</title>
+    <title>The ANTLR Plugin</title>
 
-    <para>The Antlr plugin extends the Java plugin to add support for generating parsers using <ulink url="http://www.antlr.org/">Antlr</ulink>.</para>
+    <para>The ANTLR plugin extends the Java plugin to add support for generating parsers using <ulink url="http://www.antlr.org/">ANTLR</ulink>.</para>
+
+    <note>
+        <para>
+            The ANTLR plugin only supports ANTLR version 2.
+        </para>
+    </note>
 
     <section>
         <title>Usage</title>
-        <para>To use the Antlr plugin, include in your build script:</para>
-        <sample id="useAntlrPlugin" dir="antlr" title="Using the Antlr plugin">
+        <para>To use the ANTLR plugin, include in your build script:</para>
+        <sample id="useAntlrPlugin" dir="antlr" title="Using the ANTLR plugin">
             <sourcefile file="build.gradle" snippet="use-plugin"/>
         </sample>
     </section>
 
     <section>
         <title>Tasks</title>
-        <para>The Antlr plugin adds a number of tasks to your project, as shown below.</para>
+        <para>The ANTLR plugin adds a number of tasks to your project, as shown below.</para>
 
         <table>
-            <title>Antlr plugin - tasks</title>
+            <title>ANTLR plugin - tasks</title>
             <thead>
                 <tr>
                     <td>Task name</td>
@@ -47,7 +53,7 @@
                 </td>
                 <td>-</td>
                 <td><apilink class="org.gradle.api.plugins.antlr.AntlrTask"/></td>
-                <td>Generates the source files for all production Antlr grammars.</td>
+                <td>Generates the source files for all production ANTLR grammars.</td>
             </tr>
             <tr>
                 <td>
@@ -55,7 +61,7 @@
                 </td>
                 <td>-</td>
                 <td><apilink class="org.gradle.api.plugins.antlr.AntlrTask"/></td>
-                <td>Generates the source files for all test Antlr grammars.</td>
+                <td>Generates the source files for all test ANTLR grammars.</td>
             </tr>
             <tr>
                 <td>
@@ -63,12 +69,12 @@
                 </td>
                 <td>-</td>
                 <td><apilink class="org.gradle.api.plugins.antlr.AntlrTask"/></td>
-                <td>Generates the source files for all Antlr grammars for the given source set.</td>
+                <td>Generates the source files for all ANTLR grammars for the given source set.</td>
             </tr>
         </table>
-        <para>The Antlr plugin adds the following dependencies to tasks added by the Java plugin.</para>
+        <para>The ANTLR plugin adds the following dependencies to tasks added by the Java plugin.</para>
         <table>
-            <title>Antlr plugin - additional task dependencies</title>
+            <title>ANTLR plugin - additional task dependencies</title>
             <thead>
                 <td>Task name</td>
                 <td>Depends on</td>
@@ -91,7 +97,7 @@
     <section>
         <title>Project layout</title>
         <table>
-            <title>Antlr plugin - project layout</title>
+            <title>ANTLR plugin - project layout</title>
             <thead>
                 <tr>
                     <td>Directory</td>
@@ -102,42 +108,42 @@
                 <td>
                     <filename>src/main/antlr</filename>
                 </td>
-                <td>Production Antlr grammar files.</td>
+                <td>Production ANTLR grammar files.</td>
             </tr>
             <tr>
                 <td>
                     <filename>src/test/antlr</filename>
                 </td>
-                <td>Test Antlr grammar files.</td>
+                <td>Test ANTLR grammar files.</td>
             </tr>
             <tr>
                 <td>
                     <filename>src/<replaceable>sourceSet</replaceable>/antlr</filename>
                 </td>
-                <td>Antlr grammar files for the given source set.</td>
+                <td>ANTLR grammar files for the given source set.</td>
             </tr>
         </table>
     </section>
 
     <section>
         <title>Dependency management</title>
-        <para>The Antlr plugin adds an <literal>antlr</literal> dependency configuration. You use this to declare the
-            version of Antlr you wish to use.</para>
-        <sample id="declareAntlrVersion" dir="antlr" title="Declare Antlr version">
+        <para>The ANTLR plugin adds an <literal>antlr</literal> dependency configuration. You use this to declare the
+            ANTLR dependency that you wish to use.</para>
+        <sample id="declareAntlrVersion" dir="antlr" title="Declare ANTLR version">
             <sourcefile file="build.gradle" snippet="declare-dependency"/>
         </sample>
     </section>
 
     <section>
         <title>Convention properties</title>
-        <para>The Antlr plugin does not add any convention properties.</para>
+        <para>The ANTLR plugin does not add any convention properties.</para>
     </section>
 
     <section>
         <title>Source set properties</title>
-        <para>The Antlr plugin adds the following properties to each source set in the project.</para>
+        <para>The ANTLR plugin adds the following properties to each source set in the project.</para>
         <table>
-            <title>Antlr plugin - source set properties</title>
+            <title>ANTLR plugin - source set properties</title>
             <thead>
                 <tr>
                     <td>Property name</td>
@@ -157,7 +163,7 @@
                     Not null
                 </td>
                 <td>
-                    The Antlr grammar files of this source set. Contains all <filename>.g</filename> found in the Antlr
+                    The ANTLR grammar files of this source set. Contains all <filename>.g</filename> found in the ANTLR
                     source directories, and excludes all other types of files.
                 </td>
             </tr>
@@ -172,7 +178,7 @@
                     <literal>[<replaceable>projectDir</replaceable>/src/<replaceable>name</replaceable>/antlr]</literal>
                 </td>
                 <td>
-                    The source directories containing the Antlr grammar files of this source set.
+                    The source directories containing the ANTLR grammar files of this source set.
                 </td>
             </tr>
         </table>
diff --git a/subprojects/docs/src/docs/userguide/applicationPlugin.xml b/subprojects/docs/src/docs/userguide/applicationPlugin.xml
index 29283ca..e3cd639 100644
--- a/subprojects/docs/src/docs/userguide/applicationPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/applicationPlugin.xml
@@ -95,6 +95,16 @@
                 <td><apilink class="org.gradle.api.tasks.bundling.Zip"/></td>
                 <td>Creates a full distribution ZIP archive including runtime libraries and OS specific scripts.</td>
             </tr>
+	        <tr>
+                <td>
+                    <literal>distTar</literal>
+                </td>
+                <td>
+                    <literal>jar</literal>, <literal>startScripts</literal>
+                </td>
+                <td><apilink class="org.gradle.api.tasks.bundling.Tar"/></td>
+                <td>Creates a full distribution TAR archive including runtime libraries and OS specific scripts.</td>
+            </tr>
         </table>
     </section>
 
diff --git a/subprojects/docs/src/docs/userguide/artifactMngmt.xml b/subprojects/docs/src/docs/userguide/artifactMngmt.xml
index af99029..38608ba 100644
--- a/subprojects/docs/src/docs/userguide/artifactMngmt.xml
+++ b/subprojects/docs/src/docs/userguide/artifactMngmt.xml
@@ -15,6 +15,15 @@
   -->
 <chapter id="artifact_management">
     <title>Publishing artifacts</title>
+    <note>
+        <para>
+            This chapter describes the <emphasis>original</emphasis> publishing mechanism available in Gradle 1.0: in Gradle 1.3 a new mechanism for publishing was introduced.
+            While this new mechanism is <emphasis>incubating</emphasis> and not yet complete, it introduces some new concepts and features that do (and will) make Gradle publishing even more powerful.
+        </para>
+        <para>
+            You can read about the new publishing plugins in <xref linkend="publishing_ivy"/> and <xref linkend="publishing_maven"/>.
+        </para>
+    </note>
     <section>
         <title>Introduction</title>
         <para>This chapter is about how you declare the outgoing artifacts of your project, and how to work with
diff --git a/subprojects/docs/src/docs/userguide/buildLifecycle.xml b/subprojects/docs/src/docs/userguide/buildLifecycle.xml
index a66dd09..32cbd88 100644
--- a/subprojects/docs/src/docs/userguide/buildLifecycle.xml
+++ b/subprojects/docs/src/docs/userguide/buildLifecycle.xml
@@ -43,8 +43,10 @@
             <varlistentry>
                 <term>Configuration</term>
                 <listitem>
-                    <para>The build scripts of <emphasis>all</emphasis> projects which are part of the build are
-                        executed. This configures the project objects.
+                    <para>During this phase the project objects are configured.
+                        The build scripts of <emphasis>all</emphasis> projects which are part of the build are
+                        executed. Gradle 1.4 introduces an incubating opt-in feature called 'configuration on demand'.
+                        In this mode, Gradle configures only relevant projects (see <xref linkend="sec:configuration_on_demand"/>).
                     </para>
                 </listitem>
             </varlistentry>
diff --git a/subprojects/docs/src/docs/userguide/commandLineTutorial.xml b/subprojects/docs/src/docs/userguide/commandLineTutorial.xml
index 7381d8c..1d72e07 100644
--- a/subprojects/docs/src/docs/userguide/commandLineTutorial.xml
+++ b/subprojects/docs/src/docs/userguide/commandLineTutorial.xml
@@ -157,13 +157,20 @@
         <section id="sec:listing_dependencies">
             <title>Listing project dependencies</title>
             <para id="para:commandline_dependency_report">Running <userinput>gradle dependencies</userinput>
-                gives you a list of the dependencies of the selected project, broken down by configuration.  For each
+                gives you a list of the dependencies of the selected project, broken down by configuration. For each
                 configuration, the direct and transitive dependencies of that configuration are shown in a tree. Below
                 is an example of this report:
             </para>
             <sample id="dependencyListReport" dir="userguide/tutorial/projectReports" title="Obtaining information about dependencies">
                 <output args="-q dependencies api:dependencies webapp:dependencies"/>
             </sample>
+            <para>
+                Since a dependency report can get large, it can be useful to restrict the report to a particular configuration.
+                This is achieved with the optional <userinput>--configuration</userinput> parameter:
+            </para>
+            <sample id="dependencyListReportFiltered" dir="userguide/tutorial/projectReports" title="Filtering dependency report by configuration">
+                <output args="-q api:dependencies --configuration testCompile"/>
+            </sample>
         </section>
         <section id="sec:dependency_insight">
             <title>Getting the insight into a particular dependency</title>
diff --git a/subprojects/docs/src/docs/userguide/depMngmt.xml b/subprojects/docs/src/docs/userguide/depMngmt.xml
index dbf8929..44a35c6 100644
--- a/subprojects/docs/src/docs/userguide/depMngmt.xml
+++ b/subprojects/docs/src/docs/userguide/depMngmt.xml
@@ -711,6 +711,16 @@
                 </sample>
             </section>
             <section>
+                <title>Ivy repository with Maven compatible layout</title>
+                <para>Optionally, a repository with pattern layout can have its 'organisation' part laid out in Maven style, with
+                    forward slashes replacing dots as separators. For example, the organisation <literal>my.company</literal>
+                    would then be represented as <literal>my/company</literal>.
+                </para>
+                <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository with Maven compatible layout">
+                    <sourcefile file="build.gradle" snippet="ivy-repo-with-m2compatible-layout"/>
+                </sample>
+            </section>
+            <section>
                 <title>Defining different artifact and ivy file locations for an Ivy repository</title>
                 <para>To define an Ivy repository which fetches ivy files and artifacts from different locations, you can explicitly define complete URL patterns
                       for artifacts and ivy files:
@@ -831,6 +841,82 @@ someroot/[artifact]-[revision].[ext]
                 </listitem>
             </itemizedlist>
     </section>
+    <section id='sec:dependency_metadata_manipulation'>
+        <title>Manipulating the dependency metadata during the resolution process</title>
+        <para>Gradle's dependency resolution engine will eventually offer a complete access to the
+            dependency tree during the resolution, allowing any kind of manipulation.
+            We want to get there incrementally, by solving one by one all the important use cases raised by our users.
+            Currently, it is possible to manipulate the dependency metadata in following ways:
+        </para>
+        <section id='sec:forcing_modules'>
+            <title>Forcing certain module versions</title>
+            <para>It is possible to configure dependency resolution to use a specific version for given dependency (transitive or not).
+                The dependency is identified by group and name.
+                This feature is useful when tackling version conflicts - for more information see <xref linkend='sub:version_conflicts'/>.
+                Another interesting use case that drives this feature is dealing with rogue metadata of transitive dependencies.
+                Imagine a transitive dependency that is pulled onto your classpath. This dependency has poor quality metadata
+                that leads to some problems at dependency resolution time.
+                However, there is a new version of this dependency available, a version with improved metadata information.
+                Using forced modules, you can force a better version of this dependency and hence avoid the problem.
+                For an example, see <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/>
+            </para>
+        </section>
+        <section id='sec:dependency_resolve_rules'>
+            <title>Dependency resolve rules</title>
+            <para>A dependency resolve rule is executed for each resolved dependency and offers ways
+                to manipulate the dependency metadata when the dependency is resolved.
+                This feature is incubating, not entire dependency metadata can be manipulated at this time.
+                At the moment, the dependency resolve rule can be used to manipulate the dependency version.</para>
+            <section id='sec:releasable_unit'>
+                <title>Modelling releaseable units</title>
+                <para>One of the use cases for dependency resolve rules was the requirement
+                    of resolving all dependencies from a single releasable unit with the same consistent version.
+                    Lets consider that the releasable unit is a set of libraries that are built, tested and published together.
+                    Lets imagine that the releasable unit are all libraries that have 'org.gradle' group and we want them to use a consistent version:
+                    <sample id="releasable-unit" dir="userguide/artifacts/resolutionStrategy" title="Forcing consistent version for a group of libraries">
+                        <sourcefile file="build.gradle" snippet="releasable-unit"/>
+                    </sample>
+                    For more information and code samples see <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/>.
+                </para>
+            </section>
+            <section id='sec:custom_versioning_scheme'>
+                <title>Custom versioning scheme</title>
+                <para>Another use case that supports the dependency resolve rules is a custom versioning scheme.
+                    Imagine an environment where the versions of libraries that can be declared in gradle builds are maintained and audited externally.
+                    In the build script, the developer declares dependencies using group, name and a placeholder for version, for example a String value: 'default'.
+                    The 'default' version is resolved to a specific version via a dependency resolve rule, which looks up the version in some corporate catalog.
+                    The dependency resolve rule implementation can be neatly encapsulated in a suite of corporate plugins.
+                    Here is an example of such dependency resolve rule:
+                    <sample id="custom-versioning-scheme" dir="userguide/artifacts/resolutionStrategy" title="Using a custom versioning scheme">
+                        <sourcefile file="build.gradle" snippet="custom-versioning-scheme"/>
+                    </sample>
+                    For more information and code samples see <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/>.
+                </para>
+            </section>
+            <section id='sec:blacklisting_version'>
+                <title>Blacklisting a version with a replacement</title>
+                <para>One of the interesting scenarios for dependency resolve rules is blacklisting a certain version of a dependency
+                    and providing a replacement version. This is extremely useful if an unwanted transitive dependency is pulled in to the dependency graph.
+                    In this scenario the dependency resolve rule can be used to specify a different version of an unwanted dependency.
+                    Why a transitive dependency might be unwanted? For example, it was published with troublesome metadata
+                    that causes the dependency resolution to fail. More specifically, our unwanted transitive dependency
+                    declares dependency to a library that cannot be found in any of the public repositories.
+                    There are many other reasons why certain version of a dependency might be unwanted and a different version is preferred.
+                </para><para>
+                    In below example, we will demonstrate how to avoid a dependency to 'org.cool.software:cool-library:1.2'
+                    and prefer different version of 'cool-library'. Let's imagine, version 1.2.1 contains important fixes
+                    and hence we prefer 1.2.1 over 1.2.
+                    Bear in mind that in the example below, only version 1.2 is replaced with 1.2.1.
+                    If there are other versions that participate in a conflict resolution the final version may be different.
+                    For example the 'newest' conflict resolution strategy can select version 1.3 because this version was also pulled transitively.
+                    <sample id="blacklisting_version" dir="userguide/artifacts/resolutionStrategy" title="Blacklisting a version with a replacement">
+                        <sourcefile file="build.gradle" snippet="blacklisting_version"/>
+                    </sample>
+                    For more information and code samples see <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/>.
+                </para>
+            </section>
+        </section>
+    </section>
     <section id='sec:dependency_cache'>
         <title>The dependency cache</title>
         <para>Gradle contains a highly sophisticated dependency caching mechanism, which seeks to minimise the number of remote requests made in
diff --git a/subprojects/docs/src/docs/userguide/featureLifecycle.xml b/subprojects/docs/src/docs/userguide/featureLifecycle.xml
index 6ae9411..4cdea41 100644
--- a/subprojects/docs/src/docs/userguide/featureLifecycle.xml
+++ b/subprojects/docs/src/docs/userguide/featureLifecycle.xml
@@ -14,7 +14,7 @@
   ~ limitations under the License.
   -->
 
-<chapter id="feature_lifecycle">
+<appendix id="feature_lifecycle">
     <title>The Feature Lifecycle</title>
     <para>
         Gradle is under constant development and improvement. New versions are also delivered on a regular and frequent basis
@@ -129,4 +129,4 @@
             releases, but this is not guaranteed.
         </para>
     </section>
-</chapter>
\ No newline at end of file
+</appendix>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/groovyPlugin.xml b/subprojects/docs/src/docs/userguide/groovyPlugin.xml
index f8c25d4..05558e0 100644
--- a/subprojects/docs/src/docs/userguide/groovyPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/groovyPlugin.xml
@@ -15,16 +15,11 @@
   -->
 <chapter id='groovy_plugin' xmlns:xi="http://www.w3.org/2001/XInclude">
     <title>The Groovy Plugin</title>
-    <para>The Groovy plugin extends the Java plugin to add support for Groovy projects. It can deal with Groovy-only projects and
-        with mixed Java/Groovy projects. It can even deal with Java-only projects.
-        <footnote>
-            <para>We don't recommend this, as the Groovy plugin uses the <emphasis>Groovyc</emphasis>
-                Ant task to compile the sources. For pure Java projects you might rather stick with
-                <literal>javac</literal>. In particular as you would have to supply a groovy jar for doing this.
-            </para>
-        </footnote>
-        The Groovy plugin supports joint compilation of Java and Groovy. This means that your project can contain Groovy
-        classes which use Java classes, and vice versa.
+    <para>The Groovy plugin extends the Java plugin to add support for Groovy projects. It can deal with
+        Groovy code, mixed Groovy and Java code, and even pure Java code (although we don't necessarily recommend to use it for the latter).
+        The plugin supports <emphasis>joint compilation</emphasis>, which allows to freely mix and match Groovy and Java code,
+        with dependencies in both directions. For example, a Groovy class can extend a Java class that in turn extends a Groovy class.
+        This makes it possible to use the best language for the job, and to rewrite any class in the other language if needed.
     </para>
 
     <section>
@@ -52,25 +47,25 @@
                 <td><literal>compileGroovy</literal></td>
                 <td><literal>compileJava</literal></td>
                 <td><apilink class="org.gradle.api.tasks.compile.GroovyCompile"/></td>
-                <td>Compiles production Groovy source files using groovyc.</td>
+                <td>Compiles production Groovy source files.</td>
             </tr>
             <tr>
                 <td><literal>compileTestGroovy</literal></td>
                 <td><literal>compileTestJava</literal></td>
                 <td><apilink class="org.gradle.api.tasks.compile.GroovyCompile"/></td>
-                <td>Compiles test Groovy source files using groovyc.</td>
+                <td>Compiles test Groovy source files.</td>
             </tr>
             <tr>
                 <td><literal>compile<replaceable>SourceSet</replaceable>Groovy</literal></td>
                 <td><literal>compile<replaceable>SourceSet</replaceable>Java</literal></td>
                 <td><apilink class="org.gradle.api.tasks.compile.GroovyCompile"/></td>
-                <td>Compiles the given source set's Groovy source files using groovyc.</td>
+                <td>Compiles the given source set's Groovy source files.</td>
             </tr>
             <tr>
                 <td><literal>groovydoc</literal></td>
                 <td>-</td>
                 <td><apilink class="org.gradle.api.tasks.javadoc.Groovydoc"/></td>
-                <td>Generates API documentation for the production Groovy source files using groovydoc.</td>
+                <td>Generates API documentation for the production Groovy source files.</td>
             </tr>
         </table>
         <para>The Groovy plugin adds the following dependencies to tasks added by the Java plugin.</para>
@@ -107,12 +102,12 @@
             source directories can contain Groovy <emphasis>and</emphasis> Java code. The Java source directories may
             only contain Java source code.
             <footnote>
-                <para>We are using the same conventions as introduced by Russel Winders Gant tool (<ulink
+                <para>We are using the same conventions as introduced by Russel Winder's Gant tool (<ulink
                         url='http://gant.codehaus.org'/>).
                 </para>
             </footnote>
-            None of these directories need exist or have anything in them. The Groovy plugin will compile whatever it
-            finds, and handles anything which is missing.
+            None of these directories need to exist or have anything in them; the Groovy plugin will simply compile
+            whatever it finds.
         </para>
 
         <table id='groovylayout'>
@@ -128,29 +123,29 @@
                 <td>
                     <filename>src/main/groovy</filename>
                 </td>
-                <td>Production Groovy source. May also contain Java source for joint compilation.</td>
+                <td>Production Groovy sources. May also contain Java sources for joint compilation.</td>
             </tr>
             <xi:include href="javaProjectTestLayout.xml"/>
             <tr>
                 <td>
                     <filename>src/test/groovy</filename>
                 </td>
-                <td>Test Groovy source. May also contain Java source for joint compilation.</td>
+                <td>Test Groovy sources. May also contain Java sources for joint compilation.</td>
             </tr>
             <xi:include href="javaProjectGenericLayout.xml"/>
             <tr>
                 <td>
                     <filename>src/<replaceable>sourceSet</replaceable>/groovy</filename>
                 </td>
-                <td>Groovy source for the given source set. May also contain Java source for joint compilation.</td>
+                <td>Groovy sources for the given source set. May also contain Java sources for joint compilation.</td>
             </tr>
         </table>
 
         <section>
             <title>Changing the project layout</title>
-            <para>TBD</para>
+            <para>Just like the Java plugin, the Groovy plugin allows to configure custom locations for Groovy production and test sources.</para>
             <sample id="customGroovySourceLayout" dir="groovy/customizedLayout" title="Custom Groovy source layout">
-                <sourcefile file="build.gradle" snippet="define-main"/>
+                <sourcefile file="build.gradle" snippet="custom-source-locations"/>
             </sample>
         </section>
         
@@ -158,27 +153,81 @@
 
     <section>
         <title>Dependency management</title>
-        <para>The Groovy plugin adds a dependency configuration called <literal>groovy</literal>.</para>
-        <para>Gradle is written in Groovy and allows you to write your build scripts in Groovy. But this is an internal
-            aspect of Gradle which is strictly separated from building Groovy projects. You are free to choose the Groovy
-            version your project should be build with. This Groovy version is not just used for compiling your code and
-            running your tests. The <literal>groovyc</literal> compiler and the <literal>groovydoc</literal>
-            tool are also taken from the Groovy version you provide. As usual, with freedom comes responsibility ;). You are
-            not just free to choose a Groovy version, you have to provide one. Gradle expects that the groovy libraries are
-            assigned to the <literal>groovy</literal> dependency configuration. Here is an example using the public Maven
-            repository:
+        <para>Because Gradle itself is partly implemented in Groovy, and its build language is also based on Groovy, Gradle already ships
+            with a Groovy library (1.8.6 as of Gradle 1.3). Nevertheless, Groovy projects need to explicitly add a Groovy dependency to
+            the appropriate configuration(s). This dependency, which can be the same or a different Groovy version than the one used internally by Gradle,
+            will then be used as a compile and runtime dependency for the project's Groovy code. It will also be used to execute the Groovy
+            compiler and Groovydoc tool, respectively.
+            <footnote>
+                More precisely, if a <literal>GroovyCompile</literal> or <literal>Groovydoc</literal> task's <literal>groovyClasspath</literal>
+                is empty, the plugin searches the task's <literal>classpath</literal> for a
+                <literal>groovy-all(-indy)</literal> or <literal>groovy(-indy)</literal> artifact. If it finds the former, it adds the same artifact
+                to <literal>groovyClasspath</literal>. If it finds the latter, and the project has at least one repository declared, the plugin adds
+                a corresponding repository dependency to <literal>groovyClasspath</literal>.
+            </footnote>
         </para>
-        <sample id="quickstartGroovyDependency" dir="groovy/quickstart" title="Configuration of Groovy plugin">
+        <para>
+            If Groovy is used both for production code, the Groovy dependency should be added to the <literal>compile</literal>
+            configuration:
+        </para>
+        <sample id="quickstartGroovyDependency" dir="groovy/quickstart" title="Configuration of Groovy dependency">
             <sourcefile file="build.gradle" snippet="groovy-dependency"/>
         </sample>
-        <para>And here is an example using the Groovy JARs checked into the <filename>lib</filename> directory of the source
-            tree:</para>
-        <sample id="flatDirGroovyDependency" dir="userguide/tutorial/groovyWithFlatDir" title="Configuration of Groovy plugin">
+        <para>
+            If Groovy is only used for test code, the Groovy dependency should be added to the <literal>testCompile</literal>
+            (but not the <literal>compile</literal>) configuration:
+        </para>
+        <sample id="groovyTestDependency" dir="userguide/groovy/groovyDependency" title="Configuration of Groovy test dependency">
+            <sourcefile file="build.gradle" snippet="groovy-test-dependency"/>
+        </sample>
+        <para>
+            To use the same Groovy library that ships with Gradle, declare a <literal>localGroovy()</literal> dependency. Note that
+            different Gradle versions ship with different Groovy versions; as such, using <literal>localGroovy()</literal> is less
+            safe then explicitly choosing a Groovy version.
+        </para>
+        <sample id="bundledGroovyDependency" dir="userguide/groovy/groovyDependency" title="Configuration of bundled Groovy dependency">
+            <sourcefile file="build.gradle" snippet="bundled-groovy-dependency"/>
+        </sample>
+        <para>
+            In earlier Gradle versions, the Groovy dependency was instead added to the <literal>groovy</literal> configuration.
+            This is no longer the preferred approach, but is still supported for backwards compatibility.
+        </para>
+        <sample id="groovyConfiguration" dir="userguide/groovy/groovyDependency" title="Configuration of Groovy configuration">
+            <sourcefile file="build.gradle" snippet="groovy-configuration"/>
+        </sample>
+        <para>The Groovy library doesn't necessarily have to come from a remote repository. It could also come from a local
+            <literal>lib</literal> directory, perhaps checked in to source control:</para>
+        <sample id="groovyFileDependency" dir="userguide/tutorial/groovyWithFlatDir" title="Configuration of Groovy file dependency">
             <sourcefile file="build.gradle" snippet="groovy-dependency"/>
         </sample>
+
     </section>
 
     <section>
+        <title>Adding custom GroovyCompile and Groovydoc tasks</title>
+        <para>
+            When adding custom <literal>GroovyCompile</literal> and <literal>Groovydoc</literal> tasks, it's important to understand
+            that these tasks consume Groovy in two ways: on their <literal>classpath</literal>, and on their <literal>groovyClasspath</literal>.
+            The former is the regular class path required by these tools to locate referenced classes, and will typically contain more than
+            just the Groovy library. The latter is used to load the Groovy compiler and Groovydoc tool, respectively, and shouldn't contain anything
+            other than the Groovy library and its dependencies.
+        </para>
+        <para>
+            Unless <literal>groovyClasspath</literal> is explicitly configured for a task, the Groovy (base) plugin will try to infer
+            the Groovy library to be used from the task's<literal>classpath</literal>. For example, if <literal>classpath</literal> contains
+            <literal>groovy-all-2.0.5.jar</literal>, the plugin will add the same dependency to <literal>groovyClasspath</literal>. If the project
+            has at least one repository defined, an external dependency will be added (e.g. <literal>"org.codehaus.groovy:groovy-all:2.0.5"</literal>);
+            otherwise, a file dependency will be added.
+        </para>
+        <para>
+            Note: When using the <literal>groovy</literal> rather than the <literal>groovy-all</literal> artifact, automatic configuration of
+            <literal>groovyClasspath</literal> will only work correctly if the project declares a repository that contains the <literal>groovy</literal>
+            artifact along with a descriptor (<literal>pom.xml</literal> or <literal>ivy.xml</literal>) listing its dependencies. Otherwise, only the
+            artifact itself will be added to <literal>groovyClasspath</literal>, which will likely result in a <literal>NoClassDefFoundError</literal>
+            during compilation.
+        </para>
+    </section>
+    <section>
         <title>Convention properties</title>
         <para>The Groovy plugin does not add any convention properties to the project.</para>
     </section>
@@ -272,14 +321,15 @@
     </section>
 
     <section id='sec:groovyCompile'>
-        <title>CompileGroovy</title>
-        <para>The Groovy plugin adds a <apilink class="org.gradle.api.tasks.compile.GroovyCompile"/> instance for
-            each source set in the project. The task type extends the <literal>Compile</literal>
-            task (see <xref linkend='sec:compile'/>). The compile task delegates to the Ant Groovyc task to do the
-            compile. Via the compile task you can set most of the properties of Ants Groovyc task.
+        <title>GroovyCompile</title>
+        <para>The Groovy plugin adds a <apilink class="org.gradle.api.tasks.compile.GroovyCompile"/> task for
+            each source set in the project. The task type extends the <literal>JavaCompile</literal>
+            task (see <xref linkend='sec:compile'/>). Unless <literal>groovyOptions.useAnt</literal> is set to <literal>true</literal>,
+            Gradle's native Groovy compiler integration is used. For most projects, this is the better choice than the Ant-based compiler.
+            The <literal>GroovyCompile</literal> task supports most configuration options of the official Groovy compiler.
         </para>
         <table>
-            <title>Groovy plugin - CompileGroovy properties</title>
+            <title>Groovy plugin - GroovyCompile properties</title>
             <thead>
                 <tr>
                     <td>Task Property</td>
@@ -313,7 +363,7 @@
                     <literal>groovyClasspath</literal>
                 </td>
                 <td><apilink class="org.gradle.api.file.FileCollection"/></td>
-                <td><literal>groovy</literal> configuration</td>
+                <td><literal>groovy</literal> configuration if non-empty; Groovy library found on <literal>classpath</literal> otherwise</td>
             </tr>
         </table>
     </section>
diff --git a/subprojects/docs/src/docs/userguide/javaLibraryDistributionPlugin.xml b/subprojects/docs/src/docs/userguide/javaLibraryDistributionPlugin.xml
new file mode 100644
index 0000000..9750cf6
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/javaLibraryDistributionPlugin.xml
@@ -0,0 +1,75 @@
+<chapter id='javaLibraryDistribution_plugin'>
+    <title>The Java Library Distribution Plugin</title>
+    <note>
+        <para>
+            The Java library distribution plugin is incubating and should not be considered stable.
+        </para>
+    </note>
+    <para>The Java library distribution plugin adds support for building a distribution ZIP for a Java library. The distribution contains the
+        JAR file for the library and its dependencies.
+    </para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the Java library distribution plugin, include in your build script:</para>
+        <sample id="usejavaLibraryPlugin" dir="userguide/javaLibraryDistribution" title="Using the java library distribution plugin">
+            <sourcefile file="build.gradle" snippet="use-plugin"/>
+        </sample>
+        <para>To define the name for the distribution you have to set the <literal>name</literal> property as shown below:
+        </para>
+        <sample id="useApplicationPlugin" dir="userguide/javaLibraryDistribution" title="Configure the distribution name">
+            <sourcefile file="build.gradle" snippet="name-conf"/>
+        </sample>
+
+        <para>The plugin build a distribution for your library. The distribution will package up the runtime dependencies of the library
+            All files stored in <filename>src/dist</filename> will be added to the root of the archive distribution. You can run
+            <userinput>gradle distZip</userinput> to create a ZIP containing the distribution.
+        </para>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>The Java library distribution plugin adds the following tasks to the project.</para>
+        <table>
+            <title>Java library distribution plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>distZip</literal>
+                </td>
+                <td>
+                    <literal>jar</literal>
+                </td>
+                <td>
+                    <apilink class="org.gradle.api.tasks.bundling.Zip"/>
+                </td>
+                <td>Creates a full distribution ZIP archive including runtime libraries.</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Extension properties</title>
+        <para>The Java library distribution plugin add an extension to the project, which you can use to configure its behaviour. See <apilink class="org.gradle.api.Project"/>.
+        </para>
+    </section>
+
+    <section id="java_library_distribution_resources">
+        <title>Including other resources in the distribution</title>
+        <para>
+            All of the files from the <filename>src/dist</filename> directory are copied. To include any static files in the distribution, simply arrange
+            them in the <filename>src/dist</filename> directory, or add it to the task.
+        </para>
+        <sample id="includeTaskOutputInApplicationDistribution" dir="userguide/javaLibraryDistribution" title="Include files in the distribution">
+            <sourcefile file="build.gradle" snippet="custom-distZip"/>
+        </sample>
+    </section>
+
+</chapter>
diff --git a/subprojects/docs/src/docs/userguide/javaPlugin.xml b/subprojects/docs/src/docs/userguide/javaPlugin.xml
index 8720df0..fbb563c 100644
--- a/subprojects/docs/src/docs/userguide/javaPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/javaPlugin.xml
@@ -1114,7 +1114,7 @@
 
         <section>
             <title>Test execution</title>
-            <para>Tests are executed in a separate isolated JVM. The <apilink class="org.gradle.api.tasks.testing.Test"/> task's
+            <para>Tests are executed in a separate JVM, isolated from the main build process. The <apilink class="org.gradle.api.tasks.testing.Test"/> task's
                 API allows you some control over how this happens.
             </para>
             <para>There are a number of properties which control how the test process is launched. This includes
@@ -1229,6 +1229,37 @@
             </para>
         </section>
 
+        <section id="test_reporting">
+            <title>Test reporting</title>
+
+            <para>The <literal>Test</literal> task generates the following results by default.</para>
+            <itemizedlist>
+                <listitem><para>An HTML test report.</para></listitem>
+                <listitem><para>The results in an XML format that is compatible with the Ant JUnit report task. This format is supported by many
+                    other tools, such as CI servers.</para></listitem>
+                <listitem><para>Results in an efficient binary format. The task generates the other results from these binary results.</para></listitem>
+            </itemizedlist>
+
+            <para>You can disable the HTML test report using the <apilink class="org.gradle.api.tasks.testing.Test" method="setTestReport"/> method. The other
+                results currently cannot be disabled.
+            </para>
+
+            <para>There is also a stand-alone <apilink class="org.gradle.api.tasks.testing.TestReport"/> task type which can generate the HTML test
+                report from the binary results generated by one or more <classname>Test</classname> task instances. To use this task type, you need
+                to define a <literal>destinationDir</literal> and the test results to include in the report. Here is a sample which generates a
+                combined report for the unit tests from subprojects:
+            </para>
+            <sample id="subProjectsTestReport" dir="testing/testReport" title="Creating a unit test report for subprojects">
+                <sourcefile file="build.gradle" snippet="test-report"/>
+            </sample>
+
+            <para>You should note that the <literal>TestReport</literal> type combines the results from multiple test
+                tasks, but it does not aggregate the results of individual test classes. This means that if a given test class is executed by
+                multiple test tasks, then the test report will include only one execution of that class and discard the other executions of that
+                class. This will be addressed in a future Gradle version.
+            </para>
+        </section>
+
         <section>
             <title>Convention values</title>
             <table>
diff --git a/subprojects/docs/src/docs/userguide/mavenPlugin.xml b/subprojects/docs/src/docs/userguide/mavenPlugin.xml
index ec8c526..02e441a 100644
--- a/subprojects/docs/src/docs/userguide/mavenPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/mavenPlugin.xml
@@ -196,27 +196,23 @@
                 </thead>
                 <tr>
                     <td>http</td>
-                    <td>org.apache.maven.wagon:wagon-http:1.0-beta-2</td>
+                    <td>org.apache.maven.wagon:wagon-http:2.2</td>
                 </tr>
                 <tr>
                     <td>ssh</td>
-                    <td>org.apache.maven.wagon:wagon-ssh:1.0-beta-2</td>
+                    <td>org.apache.maven.wagon:wagon-ssh:2.2</td>
                 </tr>
                 <tr>
                     <td>ssh-external</td>
-                    <td>org.apache.maven.wagon:wagon-ssh-external:1.0-beta-2</td>
-                </tr>
-                <tr>
-                    <td>scp</td>
-                    <td>org.apache.maven.wagon:wagon-scp:1.0-beta-2</td>
+                    <td>org.apache.maven.wagon:wagon-ssh-external:2.2</td>
                 </tr>
                 <tr>
                     <td>ftp</td>
-                    <td>org.apache.maven.wagon:wagon-ftp:1.0-beta-2</td>
+                    <td>org.apache.maven.wagon:wagon-ftp:2.2</td>
                 </tr>
                 <tr>
                     <td>webdav</td>
-                    <td>org.apache.maven.wagon:wagon-webdav-jackrabbit:1.0-beta-6</td>
+                    <td>org.apache.maven.wagon:wagon-webdav-jackrabbit:2.2</td>
                 </tr>
                 <tr>
                     <td>file</td>
diff --git a/subprojects/docs/src/docs/userguide/multiproject.xml b/subprojects/docs/src/docs/userguide/multiproject.xml
index 38d62e5..3a973cb 100644
--- a/subprojects/docs/src/docs/userguide/multiproject.xml
+++ b/subprojects/docs/src/docs/userguide/multiproject.xml
@@ -24,6 +24,50 @@
             its core, so the projects don't have to be java projects. Our first examples are about marine life.
         </para>
         <section>
+            <title>Configuration and execution</title>
+            <para><xref linkend="sec:build_phases"/> describes the phases of every Gradle build.
+                Let's zoom into configuration and execution phases of a multi-project build.
+                The configuration of all projects happens before any task is executed.
+                This means that when a single task, from a single project is requested,
+                <emphasis>all</emphasis> projects of multi-project build are configured first.
+                The reason every project needs to be configured is to support
+                the flexibility of accessing and changing any part of Gradle project model.
+            </para>
+            <section id='sec:configuration_on_demand'>
+                <title>Configuration on demand</title>
+                <para>Configuration injection feature and access to the complete project model
+                    are possible because every project is configured before the execution phase.
+                    Yet, this approach may not be the most efficient in a very large multi-project builds.
+                    There are Gradle builds with a hierarchy of hundreds of subprojects.
+                    Configuration time of huge multi-project builds may become noticeable.
+                    Scalability is an important requirement for Gradle. Hence, starting from version 1.4
+                    new incubating 'configuration on demand' mode is introduced.</para>
+                <para>
+                    Configuration on demand mode attempts to configure only projects that are relevant for requested tasks.
+                    This way, the configuration time of a large multi-project build is greatly improved.
+                    In the long term, this mode may become the default mode, possibly the only mode for Gradle build execution.
+                    The configuration on demand feature is incubating so not every build is guaranteed to work correctly.
+                    The feature should work very well for multi-project builds that have decoupled projects (<xref linkend="sec:decoupled_projects"/>).
+                    In configuration on demand mode projects are configured as follows:
+                    <itemizedlist>
+                        <listitem>Root project is always evaluated.
+                            This way the typical common configuration is supported (allprojects or subprojects script blocks).</listitem>
+                        <listitem>Project that lives in the directory where the build is executed is always evaluated.</listitem>
+                        <listitem>The standard project dependencies are supported.
+                            If project A has a compile dependency on project B then building A causes configuration of both projects: A and B.</listitem>
+                        <listitem>Task requested via task path causes the relevant project configured.
+                            Building 'projectA:projectB:someTask' causes configuration of both: projectA and projectB.
+                        </listitem>
+                    </itemizedlist>
+                </para>
+                <para>
+                    Down the road Gradle will offer first class command line support for configuration on demand mode.
+                    At the moment, to enable this feature please use incubating 'org.gradle.configuration.ondemand' system property.
+                    For reference on using system properties see <xref linkend="sec:gradle_properties_and_system_properties"/>.
+                </para>
+            </section>
+        </section>
+        <section>
             <title>Defining common behavior</title>
             <para>We have the following project tree. This is a multi-project build with a root project
                 <literal>water</literal> and a subproject <literal>bluewhale</literal>.
diff --git a/subprojects/docs/src/docs/userguide/publishingIvy.xml b/subprojects/docs/src/docs/userguide/publishingIvy.xml
index 4219bb5..8b62e88 100644
--- a/subprojects/docs/src/docs/userguide/publishingIvy.xml
+++ b/subprojects/docs/src/docs/userguide/publishingIvy.xml
@@ -22,16 +22,14 @@
             If you are looking for documentation on the “traditional” Ivy publishing support please see <xref linkend="artifact_management"/>.
         </para>
     </note>
-    <section>
-        <para>
-            This chapter describes how to publish build artifacts in the <ulink url="http://ant.apache.org/ivy/">Apache Ivy</ulink> format, usually to a repository
-            for consumption by other builds or projects. What is published is one or more artifacts created by the build, and an Ivy <firstterm>module descriptor</firstterm>
-            that describes the artifacts and the dependencies of the artifacts, if any.
-        </para>
-        <para>
-            A published Ivy module can be consumed by Gradle (see <xref linkend="dependency_management" />) and other tools that understand the Ivy format.
-        </para>
-    </section>
+    <para>
+        This chapter describes how to publish build artifacts in the <ulink url="http://ant.apache.org/ivy/">Apache Ivy</ulink> format, usually to a repository
+        for consumption by other builds or projects. What is published is one or more artifacts created by the build, and an Ivy <firstterm>module descriptor</firstterm>
+        that describes the artifacts and the dependencies of the artifacts, if any.
+    </para>
+    <para>
+        A published Ivy module can be consumed by Gradle (see <xref linkend="dependency_management" />) and other tools that understand the Ivy format.
+    </para>
     <section>
         <title>The “<literal>ivy-publish</literal>” Plugin</title>
         <para>
@@ -53,6 +51,9 @@
                 Establish a rule to automatically create an <apilink class="org.gradle.api.publish.ivy.tasks.PublishToIvyRepository" /> task for each Ivy publishing repository added
                 (see <xref linkend="publishing_ivy:repositories"/>)
             </listitem>
+            <listitem>
+                Create a task named 'generateIvyDescriptor' of type <apilink class="org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor" />.
+            </listitem>
         </itemizedlist>
         <para>
             The “<literal>publishing</literal>” plugin creates an extension on the project named “<literal>publishing</literal>” of type <apilink class="org.gradle.api.publish.PublishingExtension"/>.
@@ -163,25 +164,9 @@
         <para>
             Executing this task will build all of the artifacts to be published, and transfer them to the repository.
         </para>
-        <!--
-            We are cheating here. We can't use the tested output mechanism because we need to modify the sample before we can run it.
-            We could avoid this if we were able to grab the output from a test (org.gradle.api.publish.ivy.SamplesIvyPublishIntegrationTest)
-        -->
-        <para>Output of <userinput>gradle publishIvyPublicationToIvyRepository</userinput></para>
-        <screen>:subproject:compileJava
-:subproject:processResources UP-TO-DATE
-:subproject:classes
-:subproject:jar
-:compileJava
-:processResources UP-TO-DATE
-:classes
-:jar
-:sourceJar
-:publishIvyPublicationToIvyRepository
-
-BUILD SUCCESSFUL
-
-Total time: 1 sec</screen>
+        <sample dir="ivypublish-new" id="publishingIvyPublishSingle" title="Publishing via single publish task">
+            <output args='publishIvyPublicationToIvyRepository'/>
+        </sample>
     <section>
         <title>The “<literal>publish</literal>” lifecycle task</title>
         <para>
@@ -192,26 +177,32 @@ Total time: 1 sec</screen>
             In more concrete terms, executing this task will execute all <apilink class="org.gradle.api.publish.ivy.tasks.PublishToIvyRepository" /> tasks in the project.
             This is usually the most convenient way to perform a publish.
         </para>
-        <!--
-            Cheating again, see above.
-        -->
-        <para>Output of <userinput>gradle publish</userinput></para>
-        <screen>:subproject:compileJava
-:subproject:processResources UP-TO-DATE
-:subproject:classes
-:subproject:jar
-:compileJava
-:processResources UP-TO-DATE
-:classes
-:jar
-:sourceJar
-:publishIvyPublicationToIvyRepository
-:publish
-
-BUILD SUCCESSFUL
-
-Total time: 1 sec</screen>
-        </section>
+        <sample dir="ivypublish-new" id="publishingIvyPublishLifecycle" title="Publishing via “publish” lifecycle task">
+            <output args='publish'/>
+        </sample>
+    </section>
+    </section>
+    <section id="publishing_ivy:descriptor">
+        <title>Generating the Ivy module descriptor file</title>
+        <para>
+            Sometimes you want to generate the Ivy module descriptor file (normally <literal>ivy.xml</literal>) without publishing your module to an Ivy
+            repository. For this purpose, the <literal>ivy-publish</literal> plugin automatically wires in one <apilink class="org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor" /> task
+            for each registered <apilink class="org.gradle.api.publish.ivy.IvyPublication" />.
+        </para>
+        <para>
+            In the default case of a single <literal>IvyPublication</literal> named 'ivy', the <literal>ivy-publish</literal> plugin
+            will create a single task named <literal>generateIvyModuleDescriptor</literal>.
+            Where more than one publication is present, additional tasks will be created using the naming pattern
+            <literal>generate«<emphasis>NAME OF PUBLICATION</emphasis>»IvyModuleDescriptor</literal>. Thus if there is one publication named 'other' in addition
+            to the default publication, there will be 2 tasks created: <literal>generateIvyModuleDescriptor</literal> and <literal>generateOtherIvyModuleDescriptor</literal>.
+        </para>
+        <para>
+            You can specify where the generated Ivy file will be located by setting the <literal>destination</literal> property on the generate task.
+            By default this file is generated to <literal>build/publications/«<emphasis>NAME OF PUBLICATION</emphasis>»/ivy.xml</literal>.
+        </para>
+        <sample dir="ivypublish-new" id="generateIvyModuleDescriptor" title="Generating the Ivy module descriptor file">
+            <sourcefile file="build.gradle" snippet="generate" />
+        </sample>
     </section>
     <section>
         <title>Future features</title>
diff --git a/subprojects/docs/src/docs/userguide/publishingMaven.xml b/subprojects/docs/src/docs/userguide/publishingMaven.xml
new file mode 100644
index 0000000..0718ca6
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/publishingMaven.xml
@@ -0,0 +1,160 @@
+<!--
+  ~ Copyright 2012 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<chapter id="publishing_maven">
+    <title>Maven Publishing (new)</title>
+    <note>
+        <para>
+            This chapter describes the new <emphasis>incubating</emphasis> Maven publishing support introduced in Gradle 1.4.
+            If you are looking for documentation on the 'traditional' Maven publishing support please see <xref linkend="artifact_management"/>.
+        </para>
+        <para>
+            This chapter is a work in progress, and is not comprehensive or complete.
+            It is expected that you understand the concepts in <xref linkend="publishing_ivy"/> before reading this chapter.
+        </para>
+    </note>
+    <para>
+        This chapter describes how to publish build artifacts to a <ulink url="http://maven.apache.org/">Apache Maven</ulink> Repository.
+        A module published to a Maven repository can be consumed by Maven, Gradle (see <xref linkend="dependency_management" />) and other tools that understand the Maven repository format.
+    </para>
+    <note>
+        <para>
+            It is currently only possible to publish '<literal>java</literal>' projects using the <literal>maven-publish</literal> plugin. If the <literal>java</literal> plugin is not
+            applied (either directly or indirectly) then no publication will be created.
+        </para>
+    </note>
+    <section>
+        <title>The <literal>maven-publish</literal> Plugin</title>
+        <para>
+            The ability to publish in the Maven format is provided by the '<literal>maven-publish</literal>' plugin.
+        </para>
+        <sample id="publishing_maven:apply_plugin" dir="maven/publish-new" title="Applying the 'maven-publish' plugin">
+            <sourcefile file="build.gradle" snippet="use-plugin" />
+        </sample>
+        <para>
+            This plugin does the following:
+        </para>
+        <itemizedlist>
+            <listitem>Applies the <literal>publishing</literal> plugin</listitem>
+            <listitem>
+                If the project has the <literal>java</literal> plugin applied:
+                <itemizedlist>
+                    <listitem>
+                        Creates a publication in the <literal>publishing.publications</literal> container
+                        of type <apilink class="org.gradle.api.publish.maven.MavenPublication" /> named “<literal>maven</literal>”
+                        (see <xref linkend="publishing_maven:publications"/>)
+                    </listitem>
+                    <listitem>
+                        Establish a rule to automatically create an <apilink class="org.gradle.api.publish.maven.tasks.PublishToMavenRepository" /> task for each Maven publishing repository added
+                        (see <xref linkend="publishing_maven:repositories"/>)
+                    </listitem>
+                </itemizedlist>
+            </listitem>
+        </itemizedlist>
+        <para>
+            The “<literal>publishing</literal>” plugin creates an extension on the project named “<literal>publishing</literal>” of type <apilink class="org.gradle.api.publish.PublishingExtension"/>.
+            This extension provides a container of named publications and a container of named repositories. The “<literal>maven-publish</literal>” works with
+            <apilink class="org.gradle.api.publish.maven.MavenPublication"/> publications and <apilink class="org.gradle.api.artifacts.repositories.MavenArtifactRepository" /> repositories.
+        </para>
+    </section>
+    <section id="publishing_maven:publications">
+        <title>Publications</title>
+        <para>
+            When the “<literal>maven-publish</literal>” plugin is applied together with the “<literal>java</literal>”plugin, it creates a single publication named “<literal>maven</literal>”.
+            This publication will publish the artifacts of the project and the associated POM file.
+        </para>
+        <para>
+            The attributes of the generated <literal>POM</literal> file will contain identity values derived from the following project properties:
+        </para>
+        <itemizedlist>
+            <listitem><literal>groupId</literal> - <apilink class="org.gradle.api.Project" method="getGroup()" /></listitem>
+            <listitem><literal>artifactId</literal> - <apilink class="org.gradle.api.Project" method="getName()" /></listitem>
+            <listitem><literal>version</literal> - <apilink class="org.gradle.api.Project" method="getVersion()" /></listitem>
+        </itemizedlist>
+        <para>
+            Note that you can set the value of these project properties in your build script, with the exception of <literal>name</literal>.
+        </para>
+        <section>
+            <title>Modifying the published module descriptor</title>
+            <para>
+                At times, the POM file generated from the project information will need to be tweaked before publishing. The <literal>maven-publish</literal>
+                plugin provides a hook to allow such modification.
+            </para>
+            <sample dir="maven/publish-new" id="publishing_maven:pom_modification" title="Modifying the POM file">
+                <sourcefile file="build.gradle" snippet="pom-modification" />
+            </sample>
+            <para>
+                In this example we are adding a 'description' element for the generated POM. With this hook, you can modify any aspect of the pom.
+                For example, you could replace the version range for a dependency with the actual version used to produce the build.
+                This allows the pom file to describe how the module should be consumed, rather than be a description of how the module was built.
+            </para>
+            <para>
+                See <apilink class="org.gradle.api.publish.maven.MavenPom" method="withXml(org.gradle.api.Action)" /> for the relevant API reference documentation.
+            </para>
+        </section>
+    </section>
+    <section id="publishing_maven:repositories">
+        <title>Repositories</title>
+        <para>
+            Publications are published to repositories. The repositories to publish to are defined by the <apilink class="org.gradle.api.publish.PublishingExtension" method="getRepositories()" />
+            container.
+        </para>
+        <sample dir="maven/publish-new" id="publishing_maven:repositories" title="Declaring repositories to publish to">
+            <sourcefile file="build.gradle" snippet="repositories" />
+        </sample>
+        <para>
+            The DSL used to declare repositories for publication is the same DSL that is used to declare repositories to consume dependencies from,
+            <apilink class="org.gradle.api.artifacts.dsl.RepositoryHandler" />. However, in the context of Maven publication only
+            <apilink class="org.gradle.api.artifacts.repositories.MavenArtifactRepository" /> repositories can be used for publication.
+        </para>
+    </section>
+    <section id="publishing_maven:publishing">
+        <title>Performing a publish</title>
+        <para>
+            The “<literal>maven-publish</literal>” plugin automatically creates a <apilink class="org.gradle.api.publish.maven.tasks.PublishToMavenRepository" />
+            task for each <apilink class="org.gradle.api.publish.maven.MavenPublication" /> and <apilink class="org.gradle.api.artifacts.repositories.MavenArtifactRepository" />
+            combination in the <literal>publishing.publications</literal> and <literal>publishing.repositories</literal> containers respectively.
+        </para>
+        <sample dir="maven/publish-new" id="publishingMavenPublishMinimal" title="A build to publish">
+            <sourcefile file="build.gradle" snippet="minimal-build" />
+            <output args="publish"/>
+        </sample>
+        <para>
+            So in this example a single <apilink class="org.gradle.api.publish.maven.tasks.PublishToMavenRepository" /> task is be added, named '<literal>publishMavenPublicationToMavenRepository</literal>'.
+            This task is wired into the <literal>publish</literal> lifecycle task.
+            Executing <literal>gradle publish</literal> builds the POM file and all of the artifacts to be published, and transfers them to the repository.
+        </para>
+    </section>
+    <section id="publishing_maven:install">
+        <title>Publishing to Maven Local</title>
+        <para>
+            For integration with a local Maven installation, it is sometimes useful to publish the module into the local .m2 repository. In Maven parlance, this is
+            referred to as 'installing' the module. The <literal>maven-publish</literal> plugin makes this easy to do by automatically creating a
+            <apilink class="org.gradle.api.publish.maven.tasks.PublishToMavenLocal" /> task for each <apilink class="org.gradle.api.publish.maven.MavenPublication" />
+            in the <literal>publishing.publications</literal> container. Each of these tasks is wired into the <literal>publishToMavenLocal</literal> lifecycle task.
+            You do not need to have `mavenLocal` in your `publishing.repositories` section.
+        </para>
+        <sample dir="maven/publish-new" id="publishingMavenPublishLocal" title="Publish a project to the Maven local repository">
+            <sourcefile file="build.gradle" snippet="minimal-build" />
+            <output args="publishToMavenLocal"/>
+        </sample>
+        <para>
+            So in this example a single <apilink class="org.gradle.api.publish.maven.tasks.PublishToMavenLocal" /> task is be added, named '<literal>publishMavenPublicationToMavenLocal</literal>'.
+            This task is wired into the <literal>publish</literal> lifecycle task.
+            Executing <literal>gradle publishToMavenLocal</literal> builds the POM file and all of the artifacts to be published, and 'installs' them into the local Maven repository.
+        </para>
+    </section>
+</chapter>
diff --git a/subprojects/docs/src/docs/userguide/scalaPlugin.xml b/subprojects/docs/src/docs/userguide/scalaPlugin.xml
index 410d040..588d8a5 100644
--- a/subprojects/docs/src/docs/userguide/scalaPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/scalaPlugin.xml
@@ -16,10 +16,11 @@
 <chapter id="scala_plugin" xmlns:xi="http://www.w3.org/2001/XInclude">
     <title>The Scala Plugin</title>
 
-    <para>The Scala plugin extends the Java plugin to add support for Scala projects. It can deal with Scala-only
-        projects and with mixed Java/Scala projects. It can even deal with Java-only projects.
-        The Scala plugin supports joint compilation of Java and Scala source. This means your project can contain
-        Scala classes which use Java classes, and vice versa.
+    <para>The Scala plugin extends the Java plugin to add support for Scala projects. It can deal with
+        Scala code, mixed Scala and Java code, and even pure Java code (although we don't necessarily recommend to use it for the latter).
+        The plugin supports <emphasis>joint compilation</emphasis>, which allows to freely mix and match Scala and Java code,
+        with dependencies in both directions. For example, a Scala class can extend a Java class that in turn extends a Scala class.
+        This makes it possible to use the best language for the job, and to rewrite any class in the other language if needed.
     </para>
 
     <section>
@@ -47,25 +48,25 @@
                 <td><literal>compileScala</literal></td>
                 <td><literal>compileJava</literal></td>
                 <td><apilink class="org.gradle.api.tasks.scala.ScalaCompile"/></td>
-                <td>Compiles production Scala source files using scalac.</td>
+                <td>Compiles production Scala source files.</td>
             </tr>
             <tr>
                 <td><literal>compileTestScala</literal></td>
                 <td><literal>compileTestJava</literal></td>
                 <td><apilink class="org.gradle.api.tasks.scala.ScalaCompile"/></td>
-                <td>Compiles test Scala source files using scalac.</td>
+                <td>Compiles test Scala source files.</td>
             </tr>
             <tr>
                 <td><literal>compile<replaceable>SourceSet</replaceable>Scala</literal></td>
                 <td><literal>compile<replaceable>SourceSet</replaceable>Java</literal></td>
                 <td><apilink class="org.gradle.api.tasks.scala.ScalaCompile"/></td>
-                <td>Compiles the given source set's Scala source files using scalac.</td>
+                <td>Compiles the given source set's Scala source files.</td>
             </tr>
             <tr>
                 <td><literal>scaladoc</literal></td>
                 <td>-</td>
                 <td><apilink class="org.gradle.api.tasks.scala.ScalaDoc"/></td>
-                <td>Generates API documentation for the production Scala source files using scaladoc.</td>
+                <td>Generates API documentation for the production Scala source files.</td>
             </tr>
         </table>
         <para>The Scala plugin adds the following dependencies to tasks added by the Java plugin.</para>
@@ -112,8 +113,9 @@
         <title>Project layout</title>
         <para>The Scala plugin assumes the project layout shown below.  All the Scala source directories can contain
             Scala <emphasis>and</emphasis> Java code. The Java source directories may only contain Java source code.
-            None of these directories need exist or have anything in them. The Scala plugin will compile whatever it
-            finds, and handles anything which is missing.</para>
+            None of these directories need to exist or have anything in them; the Scala plugin will simply compile
+            whatever it finds.
+        </para>
         <table id='scalalayout'>
             <title>Scala plugin - project layout</title>
             <thead>
@@ -127,29 +129,29 @@
                 <td>
                     <filename>src/main/scala</filename>
                 </td>
-                <td>Production Scala source. May also contain Java source for joint compilation.</td>
+                <td>Production Scala sources. May also contain Java sources for joint compilation.</td>
             </tr>
             <xi:include href="javaProjectTestLayout.xml"/>
             <tr>
                 <td>
                     <filename>src/test/scala</filename>
                 </td>
-                <td>Test Scala source. May also contain Java source for joint compilation.</td>
+                <td>Test Scala sources. May also contain Java sources for joint compilation.</td>
             </tr>
             <xi:include href="javaProjectGenericLayout.xml"/>
             <tr>
                 <td>
                     <filename>src/<replaceable>sourceSet</replaceable>/scala</filename>
                 </td>
-                <td>Scala source for the given source set. May also contain Java source for joint compilation.</td>
+                <td>Scala sources for the given source set. May also contain Java sources for joint compilation.</td>
             </tr>
         </table>
 
         <section>
             <title>Changing the project layout</title>
-            <para>TBD</para>
+            <para>Just like the Java plugin, the Scala plugin allows to configure custom locations for Scala production and test sources.</para>
             <sample id="customScalaSourceLayout" dir="scala/customizedLayout" title="Custom Scala source layout">
-                <sourcefile file="build.gradle" snippet="define-main"/>
+                <sourcefile file="build.gradle" snippet="custom-source-locations"/>
             </sample>
         </section>
 
@@ -157,12 +159,37 @@
 
     <section>
         <title>Dependency management</title>
-        <para>The Scala plugin adds a <literal>scalaTools</literal> configuration, which it uses to locate the Scala
-            tools, such as scalac, to use. You must specify the version of Scala to use. Below is an example.
+        <para>
+            Scala projects need to add a <literal>scala-library</literal> dependency to the appropriate configuration(s). This dependency will then
+            be used as a compile and runtime dependency for the project's Scala code. It will also be used to infer a <literal>scala-compiler</literal>
+            dependency for executing the Scala compiler and Scaladoc tool.
+            <footnote>
+                More precisely, if a <literal>ScalaCompile</literal> or <literal>ScalaDoc</literal> task's <literal>scalaClasspath</literal> is empty,
+                and the project has at least one repository declared, the plugin searches the task's <literal>classpath</literal> for a
+                <literal>scala-library</literal> artifact, and adds a corresponding <literal>scala-compiler</literal> repository dependency to the
+                task's <literal>scalaClasspath</literal>.
+            </footnote>
         </para>
-        <sample id="declareScalaTools" dir="scala/quickstart" title="Declaring the Scala version to use">
-            <sourcefile file="build.gradle" snippet="declare-scala-version"/>
+        <para>
+            If Scala is used for production code, the <literal>scala-library</literal> dependency should be added to the
+            <literal>compile</literal> configuration:
+        </para>
+        <sample id="declareScalaDependency" dir="scala/quickstart" title="Declaring a Scala dependency for production code">
+            <sourcefile file="build.gradle" snippet="scala-dependency"/>
         </sample>
+        <para>
+            If Scala is only used for test code, the <literal>scala-library</literal> dependency should be added to the <literal>testCompile</literal>
+            (but not the <literal>compile</literal>)
+            configuration:
+        </para>
+        <sample id="declareScalaTestDependency" dir="userguide/scala/scalaDependency" title="Declaring a Scala dependency for test code">
+            <sourcefile file="build.gradle" snippet="scala-test-dependency"/>
+        </sample>
+        <para>
+            In earlier Gradle versions, it was necessary to add a <literal>scala-compiler</literal> dependency to the <literal>scalaTools</literal>
+            configuration. Although typically no longer necessary (because the compiler dependency is inferred), this is still supported for
+            backwards compatibility.
+        </para>
     </section>
 
     <section>
diff --git a/subprojects/docs/src/docs/userguide/standardPlugins.xml b/subprojects/docs/src/docs/userguide/standardPlugins.xml
index 46b4bb6..6fd77eb 100644
--- a/subprojects/docs/src/docs/userguide/standardPlugins.xml
+++ b/subprojects/docs/src/docs/userguide/standardPlugins.xml
@@ -32,9 +32,13 @@
             </thead>
             <tr>
                 <td>
-                    <link linkend='java_plugin'><literal>java</literal></link>
+                    <link linkend='java_plugin'>
+                        <literal>java</literal>
+                    </link>
+                </td>
+                <td>
+                    <literal>java-base</literal>
                 </td>
-                <td><literal>java-base</literal></td>
                 <td>-</td>
                 <td>
                     <para>Adds Java compilation, testing and bundling capabilities to a project. It serves
@@ -44,19 +48,28 @@
             </tr>
             <tr>
                 <td>
-                    <link linkend='groovy_plugin'><literal>groovy</literal></link>
+                    <link linkend='groovy_plugin'>
+                        <literal>groovy</literal>
+                    </link>
+                </td>
+                <td><literal>java</literal>,
+                    <literal>groovy-base</literal>
                 </td>
-                <td><literal>java</literal>, <literal>groovy-base</literal></td>
                 <td>-</td>
                 <td>
-                    <para>Adds support for building Groovy projects. See also <xref linkend="tutorial_groovy_projects"/>.</para>
+                    <para>Adds support for building Groovy projects. See also <xref linkend="tutorial_groovy_projects"/>.
+                    </para>
                 </td>
             </tr>
             <tr>
                 <td>
-                    <link linkend="scala_plugin"><literal>scala</literal></link>
+                    <link linkend="scala_plugin">
+                        <literal>scala</literal>
+                    </link>
+                </td>
+                <td><literal>java</literal>,
+                    <literal>scala-base</literal>
                 </td>
-                <td><literal>java</literal>, <literal>scala-base</literal></td>
                 <td>-</td>
                 <td>
                     <para>Adds support for building Scala projects.</para>
@@ -64,12 +77,17 @@
             </tr>
             <tr>
                 <td>
-                    <link linkend='antlr_plugin'><literal>antlr</literal></link>
+                    <link linkend='antlr_plugin'>
+                        <literal>antlr</literal>
+                    </link>
+                </td>
+                <td>
+                    <literal>java</literal>
                 </td>
-                <td><literal>java</literal></td>
                 <td>-</td>
                 <td>
-                    <para>Adds support for generating parsers using <ulink url="http://www.antlr.org/">Antlr</ulink>.</para>
+                    <para>Adds support for generating parsers using <ulink url="http://www.antlr.org/">Antlr</ulink>.
+                    </para>
                 </td>
             </tr>
         </table>
@@ -89,7 +107,9 @@
             </thead>
             <tr>
                 <td>
-                    <link linkend='cpp'><literal>cpp</literal></link>
+                    <link linkend='cpp'>
+                        <literal>cpp</literal>
+                    </link>
                 </td>
                 <td>-</td>
                 <td>-</td>
@@ -99,9 +119,13 @@
             </tr>
             <tr>
                 <td>
-                    <link linkend='cpp'><literal>cpp-exe</literal></link>
+                    <link linkend='cpp'>
+                        <literal>cpp-exe</literal>
+                    </link>
+                </td>
+                <td>
+                    <literal>cpp</literal>
                 </td>
-                <td><literal>cpp</literal></td>
                 <td>-</td>
                 <td>
                     <para>Adds C++ executable compilation and linking capabilities to a project.</para>
@@ -109,9 +133,13 @@
             </tr>
             <tr>
                 <td>
-                    <link linkend='cpp'><literal>cpp-lib</literal></link>
+                    <link linkend='cpp'>
+                        <literal>cpp-lib</literal>
+                    </link>
+                </td>
+                <td>
+                    <literal>cpp</literal>
                 </td>
-                <td><literal>cpp</literal></td>
                 <td>-</td>
                 <td>
                     <para>Adds C++ library compilation and linking capabilities to a project.</para>
@@ -134,7 +162,9 @@
             </thead>
             <tr>
                 <td>
-                    <link linkend='announce_plugin'><literal>announce</literal></link>
+                    <link linkend='announce_plugin'>
+                        <literal>announce</literal>
+                    </link>
                 </td>
                 <td>-</td>
                 <td>-</td>
@@ -144,18 +174,24 @@
             </tr>
             <tr>
                 <td>
-                    <link linkend='application_plugin'><literal>application</literal></link>
+                    <link linkend='application_plugin'>
+                        <literal>application</literal>
+                    </link>
+                </td>
+                <td>
+                    <literal>java</literal>
                 </td>
-                <td><literal>java</literal></td>
                 <td>-</td>
                 <td>
-                    <para>Adds tasks for running and bundling a project as application.
+                    <para>Adds tasks for running and bundling a Java project as a command-line application.
                     </para>
                 </td>
             </tr>
             <tr>
                 <td>
-                    <link linkend='build_announcements_plugin'><literal>build-announcements</literal></link>
+                    <link linkend='build_announcements_plugin'>
+                        <literal>build-announcements</literal>
+                    </link>
                 </td>
                 <td>announce</td>
                 <td>-</td>
@@ -165,19 +201,27 @@
             </tr>
             <tr>
                 <td>
-                    <link linkend='ear_plugin'><literal>ear</literal></link>
+                    <link linkend='ear_plugin'>
+                        <literal>ear</literal>
+                    </link>
                 </td>
                 <td>-</td>
-                <td><literal>java</literal></td>
+                <td>
+                    <literal>java</literal>
+                </td>
                 <td>
                     <para>Adds support for building J2EE applications.</para>
                 </td>
             </tr>
             <tr>
                 <td>
-                    <link linkend='jetty_plugin'><literal>jetty</literal></link>
+                    <link linkend='jetty_plugin'>
+                        <literal>jetty</literal>
+                    </link>
+                </td>
+                <td>
+                    <literal>war</literal>
                 </td>
-                <td><literal>war</literal></td>
                 <td>-</td>
                 <td>
                     <para>Deploys your web application to a Jetty web container embedded in the build. See also <xref linkend="web_project_tutorial"/>.
@@ -186,41 +230,113 @@
             </tr>
             <tr>
                 <td>
-                    <link linkend="maven_plugin"><literal>maven</literal></link>
+                    <link linkend="maven_plugin">
+                        <literal>maven</literal>
+                    </link>
                 </td>
                 <td>-</td>
-                <td><literal>java</literal>, <literal>war</literal></td>
+                <td><literal>java</literal>,
+                    <literal>war</literal>
+                </td>
                 <td>
-                    <para>Adds support for deploying artifacts to Maven repositories.</para>
+                    <para>Adds support for publishing artifacts to Maven repositories.</para>
                 </td>
             </tr>
             <tr>
                 <td>
-                    <link linkend="osgi_plugin"><literal>osgi</literal></link>
+                    <link linkend="osgi_plugin">
+                        <literal>osgi</literal>
+                    </link>
+                </td>
+                <td>
+                    <literal>java-base</literal>
+                </td>
+                <td>
+                    <literal>java</literal>
+                </td>
+                <td>
+                    <para>Adds support for building OSGi bundles.</para>
                 </td>
-                <td><literal>java-base</literal></td>
-                <td><literal>java</literal></td>
-                <td><para>Adds support for building OSGi bundles.</para></td>
             </tr>
             <tr>
                 <td>
-                    <link linkend="war_plugin"><literal>war</literal></link>
+                    <link linkend="war_plugin">
+                        <literal>war</literal>
+                    </link>
+                </td>
+                <td>
+                    <literal>java</literal>
                 </td>
-                <td><literal>java</literal></td>
                 <td>-</td>
                 <td>
                     <para>Adds support for assembling web application WAR files. See also <xref linkend="web_project_tutorial"/>.
                     </para>
                 </td>
             </tr>
+        </table>
+    </section>
+    <section>
+        <title>Incubating integration plugins</title>
+        <para>These plugins provide some integration with various build and runtime technologies.</para>
+        <table>
+            <title>Incubating integration plugins</title>
+            <thead>
+                <tr>
+                    <td>Plugin Id</td>
+                    <td>Automatically applies</td>
+                    <td>Works with</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
             <tr>
                 <td>
-                    <link linkend="bootstrap_plugin"><literal>maven2Gradle</literal></link>
+                    <link linkend="javaLibraryDistribution_plugin">
+                        <literal>java-library-distribution</literal>
+                    </link>
+                </td>
+                <td><literal>java</literal></td>
+                <td>-</td>
+                <td>
+                    <para>Adds support for building a ZIP distribution for a Java library.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend="publishing_ivy">
+                        <literal>ivy-publish</literal>
+                    </link>
                 </td>
                 <td>-</td>
                 <td>-</td>
                 <td>
-                    <para>Adds experimental support for converting an existing maven build into a Gradle project.
+                    <para>This plugin provides a new DSL to support publishing artifacts to Ivy repositories, which improves on the existing DSL.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend="publishing_maven">
+                        <literal>maven-publish</literal>
+                    </link>
+                </td>
+                <td>-</td>
+                <td><literal>java</literal>, <literal>war</literal></td>
+                <td>
+                    <para>This plugin provides a new DSL to support publishing artifacts to Maven repositories, which improves on the existing DSL.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend="bootstrap_plugin">
+                        <literal>maven2Gradle</literal>
+                    </link>
+                </td>
+                <td>-</td>
+                <td>-</td>
+                <td>
+                    <para>Adds support for converting an existing Maven build into a Gradle project.
                     </para>
                 </td>
             </tr>
@@ -241,34 +357,48 @@
             </thead>
             <tr>
                 <td>
-                    <link linkend='checkstyle_plugin'><literal>checkstyle</literal></link>
+                    <link linkend='checkstyle_plugin'>
+                        <literal>checkstyle</literal>
+                    </link>
+                </td>
+                <td>
+                    <literal>java-base</literal>
                 </td>
-                <td><literal>java-base</literal></td>
                 <td>-</td>
                 <td>
-                    <para>Performs quality checks on your project's Java source files using <ulink url="http://checkstyle.sourceforge.net/index.html">Checkstyle</ulink>
+                    <para>Performs quality checks on your project's Java source files using
+                        <ulink url="http://checkstyle.sourceforge.net/index.html">Checkstyle</ulink>
                         and generates reports from these checks.
                     </para>
                 </td>
             </tr>
             <tr>
                 <td>
-                    <link linkend='codenarc_plugin'><literal>codenarc</literal></link>
+                    <link linkend='codenarc_plugin'>
+                        <literal>codenarc</literal>
+                    </link>
+                </td>
+                <td>
+                    <literal>groovy-base</literal>
                 </td>
-                <td><literal>groovy-base</literal></td>
                 <td>-</td>
                 <td>
-                    <para>Performs quality checks on your project's Groovy source files using <ulink url="http://codenarc.sourceforge.net/index.html">CodeNarc</ulink>
+                    <para>Performs quality checks on your project's Groovy source files using
+                        <ulink url="http://codenarc.sourceforge.net/index.html">CodeNarc</ulink>
                         and generates reports from these checks.
                     </para>
                 </td>
             </tr>
             <tr>
                 <td>
-                    <link linkend='eclipse_plugin'><literal>eclipse</literal></link>
+                    <link linkend='eclipse_plugin'>
+                        <literal>eclipse</literal>
+                    </link>
                 </td>
                 <td>-</td>
-                <td><literal>java</literal>, <literal>groovy</literal>, <literal>scala</literal></td>
+                <td><literal>java</literal>,<literal>groovy</literal>,
+                    <literal>scala</literal>
+                </td>
                 <td>
                     <para>Generates files that are used by <ulink url="http://eclipse.org">Eclipse IDE</ulink>, thus making
                         it possible to import the project into Eclipse. See also <xref linkend="tutorial_java_projects"/>.
@@ -277,35 +407,48 @@
             </tr>
             <tr>
                 <td>
-                    <link linkend='eclipse_plugin'><literal>eclipse-wtp</literal></link>
+                    <link linkend='eclipse_plugin'>
+                        <literal>eclipse-wtp</literal>
+                    </link>
                 </td>
                 <td>-</td>
-                <td><literal>ear</literal>, <literal>war</literal></td>
+                <td><literal>ear</literal>,
+                    <literal>war</literal>
+                </td>
                 <td>
                     <para>Does the same as the eclipse plugin plus generates eclipse WTP (Web Tools Platform) configuration files.
                         After importing to eclipse your war/ear projects should be configured to work with WTP.
-                        See also <xref linkend="tutorial_java_projects"/>.
+                        See also<xref linkend="tutorial_java_projects"/>.
                     </para>
                 </td>
             </tr>
             <tr>
                 <td>
-                    <link linkend='findbugs_plugin'><literal>findbugs</literal></link>
+                    <link linkend='findbugs_plugin'>
+                        <literal>findbugs</literal>
+                    </link>
+                </td>
+                <td>
+                    <literal>java-base</literal>
                 </td>
-                <td><literal>java-base</literal></td>
                 <td>-</td>
                 <td>
-                    <para>Performs quality checks on your project's Java source files using <ulink url="http://findbugs.sourceforge.net">FindBugs</ulink>
+                    <para>Performs quality checks on your project's Java source files using
+                        <ulink url="http://findbugs.sourceforge.net">FindBugs</ulink>
                         and generates reports from these checks.
                     </para>
                 </td>
             </tr>
             <tr>
                 <td>
-                    <link linkend='idea_plugin'><literal>idea</literal></link>
+                    <link linkend='idea_plugin'>
+                        <literal>idea</literal>
+                    </link>
                 </td>
                 <td>-</td>
-                <td><literal>java</literal></td>
+                <td>
+                    <literal>java</literal>
+                </td>
                 <td>
                     <para>Generates files that are used by <ulink url="http://www.jetbrains.com/idea/index.html">Intellij IDEA IDE</ulink>,
                         thus making it possible to import the project into IDEA.
@@ -314,33 +457,47 @@
             </tr>
             <tr>
                 <td>
-                    <link linkend='jdepend_plugin'><literal>jdepend</literal></link>
+                    <link linkend='jdepend_plugin'>
+                        <literal>jdepend</literal>
+                    </link>
+                </td>
+                <td>
+                    <literal>java-base</literal>
                 </td>
-                <td><literal>java-base</literal></td>
                 <td>-</td>
                 <td>
-                    <para>Performs quality checks on your project's source files using <ulink url="http://clarkware.com/software/JDepend.html">JDepend</ulink>
+                    <para>Performs quality checks on your project's source files using
+                        <ulink url="http://clarkware.com/software/JDepend.html">JDepend</ulink>
                         and generates reports from these checks.
                     </para>
                 </td>
             </tr>
             <tr>
                 <td>
-                    <link linkend='pmd_plugin'><literal>pmd</literal></link>
+                    <link linkend='pmd_plugin'>
+                        <literal>pmd</literal>
+                    </link>
+                </td>
+                <td>
+                    <literal>java-base</literal>
                 </td>
-                <td><literal>java-base</literal></td>
                 <td>-</td>
                 <td>
-                    <para>Performs quality checks on your project's Java source files using <ulink url="http://pmd.sourceforge.net">PMD</ulink>
+                    <para>Performs quality checks on your project's Java source files using
+                        <ulink url="http://pmd.sourceforge.net">PMD</ulink>
                         and generates reports from these checks.
                     </para>
                 </td>
             </tr>
             <tr>
                 <td>
-                    <link linkend='project_reports_plugin'><literal>project-report</literal></link>
+                    <link linkend='project_reports_plugin'>
+                        <literal>project-report</literal>
+                    </link>
+                </td>
+                <td>
+                    <literal>reporting-base</literal>
                 </td>
-                <td><literal>reporting-base</literal></td>
                 <td>-</td>
                 <td>
                     <para>Generates reports containing useful information about your Gradle build.
@@ -349,7 +506,9 @@
             </tr>
             <tr>
                 <td>
-                    <link linkend='signing_plugin'> <literal>signing</literal> </link>
+                    <link linkend='signing_plugin'>
+                        <literal>signing</literal>
+                    </link>
                 </td>
                 <td>base</td>
                 <td>-</td>
@@ -360,12 +519,16 @@
             </tr>
             <tr>
                 <td>
-                    <link linkend='sonar_plugin'> <literal>sonar</literal> </link>
+                    <link linkend='sonar_plugin'>
+                        <literal>sonar</literal>
+                    </link>
                 </td>
                 <td>-</td>
                 <td>-</td>
                 <td>
-                    <para>Provides integration with the <ulink url="http://www.sonarsource.org">Sonar</ulink> code quality platform.
+                    <para>Provides integration with the
+                        <ulink url="http://www.sonarsource.org">Sonar</ulink>
+                        code quality platform.
                     </para>
                 </td>
             </tr>
@@ -388,42 +551,58 @@
             </thead>
             <tr>
                 <td>base</td>
-                <td><para>Adds the standard lifecycle tasks and configures reasonable defaults for the archive tasks:
-                    <itemizedlist>
-                        <listitem>adds build<replaceable>ConfigurationName</replaceable> tasks.
-                            Those tasks assemble the artifacts belonging to the specified configuration.
-                        </listitem>
-                        <listitem>adds upload<replaceable>ConfigurationName</replaceable> tasks.
-                            Those tasks assemble and upload the artifacts belonging to the specified configuration.
-                        </listitem>
-                        <listitem>configures reasonable default values for all archive tasks (e.g. tasks that inherit from
-                            <apilink class="org.gradle.api.tasks.bundling.AbstractArchiveTask"/>).
-                            For example, the archive tasks are tasks of types: <apilink class="org.gradle.api.tasks.bundling.Jar"/>,
-                            <apilink class="org.gradle.api.tasks.bundling.Tar"/>, <apilink class="org.gradle.api.tasks.bundling.Zip"/>.
-                            Specifically, <literal>destinationDir</literal>, <literal>baseName</literal> and <literal>version</literal>
-                            properties of the archive tasks are preconfigured with defaults.
-                            This is extremely useful because it drives consistency across projects;
-                            the consistency regarding naming conventions of archives and their location after the build completed.
-                        </listitem>
-                    </itemizedlist>
+                <td>
+                    <para>Adds the standard lifecycle tasks and configures reasonable defaults for the archive tasks:
+                        <itemizedlist>
+                            <listitem>adds build
+                                <replaceable>ConfigurationName</replaceable>
+                                tasks.
+                                Those tasks assemble the artifacts belonging to the specified configuration.
+                            </listitem>
+                            <listitem>adds upload
+                                <replaceable>ConfigurationName</replaceable>
+                                tasks.
+                                Those tasks assemble and upload the artifacts belonging to the specified configuration.
+                            </listitem>
+                            <listitem>configures reasonable default values for all archive tasks (e.g. tasks that inherit from
+                                <apilink class="org.gradle.api.tasks.bundling.AbstractArchiveTask"/>).
+                                For example, the archive tasks are tasks of types: <apilink class="org.gradle.api.tasks.bundling.Jar"/>,
+                                <apilink class="org.gradle.api.tasks.bundling.Tar"/>, <apilink class="org.gradle.api.tasks.bundling.Zip"/>.
+                                Specifically, <literal>destinationDir</literal>,
+                                <literal>baseName</literal>
+                                and
+                                <literal>version</literal>
+                                properties of the archive tasks are preconfigured with defaults.
+                                This is extremely useful because it drives consistency across projects;
+                                the consistency regarding naming conventions of archives and their location after the build completed.
+                            </listitem>
+                        </itemizedlist>
                     </para>
                 </td>
             </tr>
             <tr>
                 <td>java-base</td>
-                <td><para>Adds the source sets concept to the project. Does not add any particular source sets.</para></td>
+                <td>
+                    <para>Adds the source sets concept to the project. Does not add any particular source sets.</para>
+                </td>
             </tr>
             <tr>
                 <td>groovy-base</td>
-                <td><para>Adds the Groovy source sets concept to the project.</para></td>
+                <td>
+                    <para>Adds the Groovy source sets concept to the project.</para>
+                </td>
             </tr>
             <tr>
                 <td>scala-base</td>
-                <td><para>Adds the Scala source sets concept to the project.</para></td>
+                <td>
+                    <para>Adds the Scala source sets concept to the project.</para>
+                </td>
             </tr>
             <tr>
                 <td>reporting-base</td>
-                <td><para>Adds some shared convention properties to the project, relating to report generation.</para></td>
+                <td>
+                    <para>Adds some shared convention properties to the project, relating to report generation.</para>
+                </td>
             </tr>
         </table>
     </section>
diff --git a/subprojects/docs/src/docs/userguide/userguide.xml b/subprojects/docs/src/docs/userguide/userguide.xml
old mode 100644
new mode 100755
index 4e910c9..4b664b3
--- a/subprojects/docs/src/docs/userguide/userguide.xml
+++ b/subprojects/docs/src/docs/userguide/userguide.xml
@@ -69,6 +69,7 @@
     <xi:include href='announcePlugin.xml'/>
     <xi:include href='buildAnnouncementsPlugin.xml'/>
     <xi:include href='applicationPlugin.xml'/>
+    <xi:include href='javaLibraryDistributionPlugin.xml'/>
     <xi:include href='bootstrapPlugin.xml'/>
 	<xi:include href='depMngmt.xml'/>
     <xi:include href='artifactMngmt.xml'/>
@@ -85,6 +86,7 @@
     <xi:include href='embedding.xml'/>
     <xi:include href='comparingBuilds.xml'/>
     <xi:include href='publishingIvy.xml'/>
+    <xi:include href='publishingMaven.xml'/>
     <!-- this is generated -->
     <xi:include href='../../../build/src/samplesList.xml'/>
     <xi:include href='potentialTraps.xml'/>
diff --git a/subprojects/docs/src/samples/antlr/build.gradle b/subprojects/docs/src/samples/antlr/build.gradle
index fb5f522..bd6ad26 100644
--- a/subprojects/docs/src/samples/antlr/build.gradle
+++ b/subprojects/docs/src/samples/antlr/build.gradle
@@ -10,7 +10,7 @@ repositories {
 dependencies {
     antlr 'antlr:antlr:2.7.7'
 // END SNIPPET declare-dependency
-    testCompile 'junit:junit:4.8.2'
+    testCompile 'junit:junit:4.11'
 // START SNIPPET declare-dependency
 }
 // END SNIPPET declare-dependency
diff --git a/subprojects/docs/src/samples/clientModuleDependencies/build.gradle b/subprojects/docs/src/samples/clientModuleDependencies/build.gradle
index 23ce57a..011e524 100644
--- a/subprojects/docs/src/samples/clientModuleDependencies/build.gradle
+++ b/subprojects/docs/src/samples/clientModuleDependencies/build.gradle
@@ -4,7 +4,7 @@ subprojects {
             mavenCentral()
         }
         dependencies {
-            classpath 'junit:junit:4.8.2'
+            classpath 'junit:junit:4.11'
         }
     }
     apply plugin: 'java'
diff --git a/subprojects/docs/src/samples/codeQuality/build.gradle b/subprojects/docs/src/samples/codeQuality/build.gradle
index fc2cc3f..63d401d 100755
--- a/subprojects/docs/src/samples/codeQuality/build.gradle
+++ b/subprojects/docs/src/samples/codeQuality/build.gradle
@@ -22,5 +22,5 @@ repositories {
 
 dependencies {
     groovy localGroovy()
-    testCompile 'junit:junit:4.8.2'
+    testCompile 'junit:junit:4.11'
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/customPlugin/plugin/build.gradle b/subprojects/docs/src/samples/customPlugin/plugin/build.gradle
index 71676d1..5eb4b90 100644
--- a/subprojects/docs/src/samples/customPlugin/plugin/build.gradle
+++ b/subprojects/docs/src/samples/customPlugin/plugin/build.gradle
@@ -22,7 +22,7 @@ repositories {
 }
 
 dependencies {
-    testCompile 'junit:junit:4.8.2'
+    testCompile 'junit:junit:4.11'
 }
 
 group = 'org.gradle'
diff --git a/subprojects/docs/src/samples/groovy/customizedLayout/build.gradle b/subprojects/docs/src/samples/groovy/customizedLayout/build.gradle
index 6f7ba78..a703a01 100644
--- a/subprojects/docs/src/samples/groovy/customizedLayout/build.gradle
+++ b/subprojects/docs/src/samples/groovy/customizedLayout/build.gradle
@@ -5,23 +5,22 @@ repositories {
 }
 
 dependencies {
-    groovy group: 'org.codehaus.groovy', name: 'groovy-all', version: '1.7.10'
-    testCompile group: 'junit', name: 'junit', version: '4.8.2'
+    compile 'org.codehaus.groovy:groovy-all:2.0.5'
+    testCompile 'junit:junit:4.11'
 }
 
-// START SNIPPET define-main
+// START SNIPPET custom-source-locations
 sourceSets {
     main {
         groovy {
-            srcDir 'src/groovy'
+            srcDirs = ['src/groovy']
         }
     }
-// END SNIPPET define-main
+
     test {
         groovy {
-            srcDir 'test/groovy'
+            srcDirs = ['test/groovy']
         }
     }
-// START SNIPPET define-main
 }
-// END SNIPPET define-main
+// END SNIPPET custom-source-locations
diff --git a/subprojects/docs/src/samples/groovy/mixedJavaAndGroovy/build.gradle b/subprojects/docs/src/samples/groovy/mixedJavaAndGroovy/build.gradle
index aeb9e20..637227e 100644
--- a/subprojects/docs/src/samples/groovy/mixedJavaAndGroovy/build.gradle
+++ b/subprojects/docs/src/samples/groovy/mixedJavaAndGroovy/build.gradle
@@ -6,6 +6,6 @@ repositories {
 }
 
 dependencies {
-    groovy group: 'org.codehaus.groovy', name: 'groovy-all', version: '1.7.10'
-    testCompile group: 'junit', name: 'junit', version: '4.8.2'
+    compile 'org.codehaus.groovy:groovy-all:2.0.5'
+    testCompile 'junit:junit:4.11'
 }
diff --git a/subprojects/docs/src/samples/groovy/multiproject/buildSrc/build.gradle b/subprojects/docs/src/samples/groovy/multiproject/buildSrc/build.gradle
index da3d5c5..fd954f4 100644
--- a/subprojects/docs/src/samples/groovy/multiproject/buildSrc/build.gradle
+++ b/subprojects/docs/src/samples/groovy/multiproject/buildSrc/build.gradle
@@ -3,5 +3,5 @@ repositories {
 }
 
 dependencies {
-    testCompile group: 'junit', name: 'junit', version: '4.8.2'
+    testCompile 'junit:junit:4.11'
 }
diff --git a/subprojects/docs/src/samples/groovy/multiproject/groovycDetector/build.gradle b/subprojects/docs/src/samples/groovy/multiproject/groovycDetector/build.gradle
index 027f3ae..36494f7 100644
--- a/subprojects/docs/src/samples/groovy/multiproject/groovycDetector/build.gradle
+++ b/subprojects/docs/src/samples/groovy/multiproject/groovycDetector/build.gradle
@@ -3,5 +3,5 @@ apply plugin: 'java'
 version = 'SNAPSHOT'
 
 dependencies {
-    compile 'org.codehaus.groovy:groovy-all:1.8.8'
+    compile 'org.codehaus.groovy:groovy-all:2.0.5'
 }
diff --git a/subprojects/docs/src/samples/groovy/multiproject/groovycDetector/src/main/java/org/gradle/test/DetectorTransform.java b/subprojects/docs/src/samples/groovy/multiproject/groovycDetector/src/main/java/org/gradle/test/DetectorTransform.java
index f1bae89..da963d4 100644
--- a/subprojects/docs/src/samples/groovy/multiproject/groovycDetector/src/main/java/org/gradle/test/DetectorTransform.java
+++ b/subprojects/docs/src/samples/groovy/multiproject/groovycDetector/src/main/java/org/gradle/test/DetectorTransform.java
@@ -12,6 +12,7 @@ import org.codehaus.groovy.control.messages.SimpleMessage;
 import org.codehaus.groovy.transform.ASTTransformation;
 import org.codehaus.groovy.transform.GroovyASTTransformation;
 import org.codehaus.groovy.runtime.InvokerHelper;
+import org.codehaus.groovy.util.ReleaseInfo;
 
 import java.util.List;
 
@@ -40,7 +41,7 @@ public class DetectorTransform implements ASTTransformation {
     for (ClassNode clazz : (List<ClassNode>)module.getClasses()) {
       FieldNode field = clazz.getField(VERSION_FIELD_NAME);
       if (field != null) {
-        field.setInitialValueExpression(new ConstantExpression(InvokerHelper.getVersion()));
+        field.setInitialValueExpression(new ConstantExpression(ReleaseInfo.getVersion()));
         break;
       }
     }
diff --git a/subprojects/docs/src/samples/groovy/multiproject/testproject/build.gradle b/subprojects/docs/src/samples/groovy/multiproject/testproject/build.gradle
index 864b80f..b6a43dd 100644
--- a/subprojects/docs/src/samples/groovy/multiproject/testproject/build.gradle
+++ b/subprojects/docs/src/samples/groovy/multiproject/testproject/build.gradle
@@ -4,9 +4,9 @@ group = 'org.gradle'
 version = '1.0'
 
 dependencies {
-    groovy 'org.codehaus.groovy:groovy-all:1.8.8'
+    compile 'org.codehaus.groovy:groovy-all:2.0.5'
     compile project(':groovycDetector')
-    testCompile 'junit:junit:4.8.2'
+    testCompile 'junit:junit:4.11'
 }
 
 sourceSets {
diff --git a/subprojects/docs/src/samples/groovy/multiproject/testproject/src/test/groovy/org/gradle/VersionTest.groovy b/subprojects/docs/src/samples/groovy/multiproject/testproject/src/test/groovy/org/gradle/VersionTest.groovy
index a1ddd3f..e352ea8 100644
--- a/subprojects/docs/src/samples/groovy/multiproject/testproject/src/test/groovy/org/gradle/VersionTest.groovy
+++ b/subprojects/docs/src/samples/groovy/multiproject/testproject/src/test/groovy/org/gradle/VersionTest.groovy
@@ -7,7 +7,7 @@ class GroovycVersionTest {
   def groovycVersion
 
   @Test
-  void versionShouldBe1_8_8() {
-    assertEquals("1.8.8", groovycVersion)
+  void versionShouldBe2_0_5() {
+    assertEquals("2.0.5", groovycVersion)
   }
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/groovy/quickstart/build.gradle b/subprojects/docs/src/samples/groovy/quickstart/build.gradle
index dee9f6f..59018f1 100644
--- a/subprojects/docs/src/samples/groovy/quickstart/build.gradle
+++ b/subprojects/docs/src/samples/groovy/quickstart/build.gradle
@@ -9,9 +9,9 @@ repositories {
 }
 
 dependencies {
-    groovy group: 'org.codehaus.groovy', name: 'groovy', version: '1.7.10'
+    compile 'org.codehaus.groovy:groovy-all:2.0.5'
 // END SNIPPET groovy-dependency
-    testCompile group: 'junit', name: 'junit', version: '4.8.2'
+    testCompile 'junit:junit:4.11'
 // START SNIPPET groovy-dependency
 }
 // END SNIPPET groovy-dependency
diff --git a/subprojects/docs/src/samples/groovy/quickstart/src/test/groovy/org/gradle/PersonTest.groovy b/subprojects/docs/src/samples/groovy/quickstart/src/test/groovy/org/gradle/PersonTest.groovy
index 2a6c78d..b86fdda 100644
--- a/subprojects/docs/src/samples/groovy/quickstart/src/test/groovy/org/gradle/PersonTest.groovy
+++ b/subprojects/docs/src/samples/groovy/quickstart/src/test/groovy/org/gradle/PersonTest.groovy
@@ -15,7 +15,7 @@ class PersonTest {
     }
 
     @Test public void usingCorrectVersionOfGroovy() {
-        assertEquals('1.7.10', GroovySystem.version)
+        assertEquals('2.0.5', GroovySystem.version)
     }
     
     @Test public void testResourcesAreAvailable() {
diff --git a/subprojects/docs/src/samples/ivypublish-new/build.gradle b/subprojects/docs/src/samples/ivypublish-new/build.gradle
index 8756493..68c0d3a 100644
--- a/subprojects/docs/src/samples/ivypublish-new/build.gradle
+++ b/subprojects/docs/src/samples/ivypublish-new/build.gradle
@@ -8,7 +8,7 @@ version = '1.0'
 group = 'org.gradle.test'
 
 dependencies {
-   compile 'junit:junit:4.8.2', project(':subproject')
+   compile 'junit:junit:4.11', project(':subproject')
 }
 
 repositories {
@@ -34,10 +34,10 @@ publishing {
 // END SNIPPET input
     repositories {
         ivy {
-            url "http://mycompany.com/repo" // change to point to your repo
+            url "file://$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
             credentials {
-                username "user1"
-                password "secret"
+                username 'user1'
+                password 'secret'
             }
         }
     }
@@ -58,3 +58,8 @@ publishing {
 // END SNIPPET repositories
 // END SNIPPET descriptor-mod
 // END SNIPPET input
+// START SNIPPET generate
+generateIvyModuleDescriptor {
+    destination = file('generated-ivy.xml')
+}
+// END SNIPPET generate
diff --git a/subprojects/docs/src/samples/ivypublish-new/output-ivy.xml b/subprojects/docs/src/samples/ivypublish-new/output-ivy.xml
index 2e395c1..89b2c6e 100644
--- a/subprojects/docs/src/samples/ivypublish-new/output-ivy.xml
+++ b/subprojects/docs/src/samples/ivypublish-new/output-ivy.xml
@@ -8,8 +8,6 @@
     <conf name="compile" visibility="private" description="Classpath for compiling the main sources."/>
     <conf name="default" visibility="public" description="Configuration for default artifacts." extends="runtime"/>
     <conf name="runtime" visibility="private" description="Classpath for running the compiled main classes." extends="compile"/>
-    <conf name="testCompile" visibility="private" description="Classpath for compiling the test sources." extends="compile"/>
-    <conf name="testRuntime" visibility="private" description="Classpath for running the compiled test classes." extends="runtime,testCompile"/>
   </configurations>
   <publications>
     <artifact name="ivypublish" type="jar" ext="jar" conf="archives,runtime"/>
diff --git a/subprojects/docs/src/samples/ivypublish/build.gradle b/subprojects/docs/src/samples/ivypublish/build.gradle
index 32ea2e7..c0e17f2 100644
--- a/subprojects/docs/src/samples/ivypublish/build.gradle
+++ b/subprojects/docs/src/samples/ivypublish/build.gradle
@@ -7,7 +7,7 @@ version = '1.0'
 group = 'org.gradle.test'
 
 dependencies {
-   compile 'junit:junit:4.8.2', project(':subproject')
+   compile 'junit:junit:4.11', project(':subproject')
 }
 
 ant {
@@ -49,7 +49,7 @@ uploadArchives {
         def root = new XmlParser().parse(new File(repoDir, 'ivy.xml'))
         assert root.publications.artifact.find { it. at name == 'ivypublishSource' }.attribute(ns.classifier) == 'src'
         assert (root.configurations.conf.collect { it. at name } as Set) == ['archives', 'compile', 'default', 'runtime', 'testCompile', 'testRuntime'] as Set
-        assert root.dependencies.dependency.find { it. at org == 'junit' }.attributes() == [org: 'junit', name: 'junit', rev: '4.8.2', conf: 'compile->default']
+        assert root.dependencies.dependency.find { it. at org == 'junit' }.attributes() == [org: 'junit', name: 'junit', rev: '4.11', conf: 'compile->default']
         assert root.dependencies.dependency.find { it. at org == 'ivypublish' }.attributes() == [org: 'ivypublish', name: 'subproject', rev: 'unspecified',
                 conf: 'compile->default']
     }
diff --git a/subprojects/docs/src/samples/java/base/test/build.gradle b/subprojects/docs/src/samples/java/base/test/build.gradle
index fc4b45f..ad435a7 100644
--- a/subprojects/docs/src/samples/java/base/test/build.gradle
+++ b/subprojects/docs/src/samples/java/base/test/build.gradle
@@ -1,5 +1,5 @@
 dependencies {
-    compile group: 'junit', name: 'junit', version: '4.8.2', project(':prod')
+    compile 'junit:junit:4.11', project(':prod')
 }
 
 task test(type: Test) {
diff --git a/subprojects/docs/src/samples/java/customizedLayout/build.gradle b/subprojects/docs/src/samples/java/customizedLayout/build.gradle
index c606bcf..97b2f8b 100644
--- a/subprojects/docs/src/samples/java/customizedLayout/build.gradle
+++ b/subprojects/docs/src/samples/java/customizedLayout/build.gradle
@@ -5,7 +5,7 @@ repositories {
 }
 
 dependencies {
-    testCompile group: 'junit', name: 'junit', version: '4.8.2'
+    testCompile 'junit:junit:4.11'
 }
 
 // START SNIPPET define-main
diff --git a/subprojects/docs/src/samples/java/multiproject/build.gradle b/subprojects/docs/src/samples/java/multiproject/build.gradle
index 1573a9b..50a5f06 100644
--- a/subprojects/docs/src/samples/java/multiproject/build.gradle
+++ b/subprojects/docs/src/samples/java/multiproject/build.gradle
@@ -8,7 +8,7 @@ subprojects {
     }
 
     dependencies {
-        testCompile 'junit:junit:4.8.2'
+        testCompile 'junit:junit:4.11'
     }
 
     version = '1.0'
diff --git a/subprojects/docs/src/samples/java/multiproject/buildSrc/build.gradle b/subprojects/docs/src/samples/java/multiproject/buildSrc/build.gradle
index da3d5c5..fd954f4 100644
--- a/subprojects/docs/src/samples/java/multiproject/buildSrc/build.gradle
+++ b/subprojects/docs/src/samples/java/multiproject/buildSrc/build.gradle
@@ -3,5 +3,5 @@ repositories {
 }
 
 dependencies {
-    testCompile group: 'junit', name: 'junit', version: '4.8.2'
+    testCompile 'junit:junit:4.11'
 }
diff --git a/subprojects/docs/src/samples/java/onlyif/build.gradle b/subprojects/docs/src/samples/java/onlyif/build.gradle
index be33abb..d0be4ef 100644
--- a/subprojects/docs/src/samples/java/onlyif/build.gradle
+++ b/subprojects/docs/src/samples/java/onlyif/build.gradle
@@ -5,7 +5,7 @@ repositories {
 }
 
 dependencies {
-    testCompile group: 'junit', name: 'junit', version: '4.8.2'
+    testCompile 'junit:junit:4.11'
 }
 
 test {
diff --git a/subprojects/docs/src/samples/java/withIntegrationTests/build.gradle b/subprojects/docs/src/samples/java/withIntegrationTests/build.gradle
index 2c397e2..58643eb 100644
--- a/subprojects/docs/src/samples/java/withIntegrationTests/build.gradle
+++ b/subprojects/docs/src/samples/java/withIntegrationTests/build.gradle
@@ -12,8 +12,8 @@ sourceSets {
 }
 
 dependencies {
-    testCompile group: 'junit', name: 'junit', version: '4.8.2'
-    integrationTestCompile group: 'commons-collections', name: 'commons-collections', version: '3.2'
+    testCompile 'junit:junit:4.11'
+    integrationTestCompile 'commons-collections:commons-collections:3.2'
     integrationTestCompile sourceSets.main.output
     integrationTestCompile configurations.testCompile
     integrationTestCompile sourceSets.test.output
diff --git a/subprojects/docs/src/samples/maven/publish-new/build.gradle b/subprojects/docs/src/samples/maven/publish-new/build.gradle
new file mode 100644
index 0000000..b0288bc
--- /dev/null
+++ b/subprojects/docs/src/samples/maven/publish-new/build.gradle
@@ -0,0 +1,39 @@
+// START SNIPPET minimal-build
+apply plugin: 'java'
+// START SNIPPET use-plugin
+apply plugin: 'maven-publish'
+// END SNIPPET use-plugin
+
+group = 'org.gradle.sample'
+version = '1.0'
+
+dependencies {
+   compile 'commons-collections:commons-collections:3.0'
+}
+
+repositories {
+    mavenCentral()
+}
+
+publishing {
+// START SNIPPET repositories
+    repositories {
+        maven {
+            url "file://$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
+        }
+    }
+// END SNIPPET repositories
+// END SNIPPET minimal-build
+// START SNIPPET pom-modification
+    publications {
+        maven {
+            pom.withXml {
+                asNode().appendNode('description', 'A demonstration of maven pom customisation')
+            }
+        }
+    }
+// END SNIPPET pom-modification
+// START SNIPPET minimal-build
+}
+// END SNIPPET minimal-build
+
diff --git a/subprojects/docs/src/samples/maven/publish-new/src/main/java/org/MyClass.java b/subprojects/docs/src/samples/maven/publish-new/src/main/java/org/MyClass.java
new file mode 100644
index 0000000..81d5a85
--- /dev/null
+++ b/subprojects/docs/src/samples/maven/publish-new/src/main/java/org/MyClass.java
@@ -0,0 +1,5 @@
+package org;
+
+public class MyClass {
+    
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/scala/customizedLayout/build.gradle b/subprojects/docs/src/samples/scala/customizedLayout/build.gradle
index 9d1b2ce..c65d6d2 100644
--- a/subprojects/docs/src/samples/scala/customizedLayout/build.gradle
+++ b/subprojects/docs/src/samples/scala/customizedLayout/build.gradle
@@ -5,24 +5,21 @@ repositories {
 }
 
 dependencies {
-    scalaTools 'org.scala-lang:scala-compiler:2.9.1'
-    compile 'org.scala-lang:scala-library:2.9.1'
-    testCompile 'junit:junit:4.8.2'
+    compile 'org.scala-lang:scala-library:2.9.2'
+    testCompile 'junit:junit:4.11'
 }
 
-// START SNIPPET define-main
+// START SNIPPET custom-source-locations
 sourceSets {
     main {
         scala {
-            srcDir 'src/scala'
+            srcDirs = ['src/scala']
         }
     }
-// END SNIPPET define-main
     test {
         scala {
-            srcDir 'test/scala'
+            srcDirs = ['test/scala']
         }
     }
-// START SNIPPET define-main
 }
-// END SNIPPET define-main
+// END SNIPPET custom-source-locations
diff --git a/subprojects/docs/src/samples/scala/fsc/build.gradle b/subprojects/docs/src/samples/scala/fsc/build.gradle
index 7f117f0..9fbb9de 100644
--- a/subprojects/docs/src/samples/scala/fsc/build.gradle
+++ b/subprojects/docs/src/samples/scala/fsc/build.gradle
@@ -5,16 +5,12 @@ repositories {
 }
 
 dependencies {
-    // Scala compiler and related tools
-    scalaTools 'org.scala-lang:scala-compiler:2.9.1'
-
-    // Scala standard library
-    compile 'org.scala-lang:scala-library:2.9.1'
+    compile 'org.scala-lang:scala-library:2.9.2'
 }
 
 dependencies {
     compile 'commons-collections:commons-collections:3.2'
-    testCompile 'junit:junit:4.8.2'
+    testCompile 'junit:junit:4.11'
 }
 
 // START SNIPPET use-fsc
diff --git a/subprojects/docs/src/samples/scala/mixedJavaAndScala/build.gradle b/subprojects/docs/src/samples/scala/mixedJavaAndScala/build.gradle
index a686380..a36592c 100644
--- a/subprojects/docs/src/samples/scala/mixedJavaAndScala/build.gradle
+++ b/subprojects/docs/src/samples/scala/mixedJavaAndScala/build.gradle
@@ -7,7 +7,6 @@ repositories {
 }
 
 dependencies {
-    scalaTools 'org.scala-lang:scala-compiler:2.9.1'
     compile 'org.scala-lang:scala-library:2.9.1'
-    testCompile 'junit:junit:4.8.2'
+    testCompile 'junit:junit:4.11'
 }
diff --git a/subprojects/docs/src/samples/scala/quickstart/build.gradle b/subprojects/docs/src/samples/scala/quickstart/build.gradle
index d2f8e1e..93ab85d 100644
--- a/subprojects/docs/src/samples/scala/quickstart/build.gradle
+++ b/subprojects/docs/src/samples/scala/quickstart/build.gradle
@@ -3,21 +3,17 @@ apply plugin: 'eclipse'
 apply plugin: 'scala'
 // END SNIPPET use-plugin
 
-// START SNIPPET declare-scala-version
+// START SNIPPET scala-dependency
 repositories {
     mavenCentral()
 }
 
 dependencies {
-    // Scala compiler and related tools
-    scalaTools 'org.scala-lang:scala-compiler:2.9.1'
-
-    // Scala standard library
     compile 'org.scala-lang:scala-library:2.9.1'
 }
-// END SNIPPET declare-scala-version
+// END SNIPPET scala-dependency
 
 dependencies {
     compile 'commons-collections:commons-collections:3.2'
-    testCompile 'junit:junit:4.8.2'
+    testCompile 'junit:junit:4.11'
 }
diff --git a/subprojects/docs/src/samples/scala/zinc/build.gradle b/subprojects/docs/src/samples/scala/zinc/build.gradle
index 05257d5..4920f5f 100644
--- a/subprojects/docs/src/samples/scala/zinc/build.gradle
+++ b/subprojects/docs/src/samples/scala/zinc/build.gradle
@@ -5,16 +5,12 @@ repositories {
 }
 
 dependencies {
-    // Scala compiler and related tools
-    scalaTools 'org.scala-lang:scala-compiler:2.9.1'
-
-    // Scala standard library
-    compile 'org.scala-lang:scala-library:2.9.1'
+    compile 'org.scala-lang:scala-library:2.9.2'
 }
 
 dependencies {
     compile 'commons-collections:commons-collections:3.2'
-    testCompile 'junit:junit:4.8.2'
+    testCompile 'junit:junit:4.11'
 }
 
 // START SNIPPET use-zinc
diff --git a/subprojects/docs/src/samples/testing/testReport/build.gradle b/subprojects/docs/src/samples/testing/testReport/build.gradle
new file mode 100644
index 0000000..c9cdcf3
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/testReport/build.gradle
@@ -0,0 +1,26 @@
+// START SNIPPET test-report
+subprojects {
+    apply plugin: 'java'
+
+// END SNIPPET test-report
+    repositories {
+        mavenCentral()
+    }
+
+    dependencies {
+        testCompile 'junit:junit:4.11'
+    }
+
+// START SNIPPET test-report
+    // Disable the test report for the individual test task
+    test {
+        testReport = false
+    }
+}
+
+task testReport(type: TestReport) {
+    destinationDir = file("$buildDir/reports/allTests")
+    // Include the results from the `test` task in all subprojects
+    reportOn subprojects*.test
+}
+// END SNIPPET test-report
diff --git a/subprojects/docs/src/samples/testing/testReport/core/src/test/java/org/gradle/sample/CoreTest.java b/subprojects/docs/src/samples/testing/testReport/core/src/test/java/org/gradle/sample/CoreTest.java
new file mode 100644
index 0000000..05c68f8
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/testReport/core/src/test/java/org/gradle/sample/CoreTest.java
@@ -0,0 +1,11 @@
+package org.gradle.sample;
+
+import org.junit.Test;
+import java.lang.System;
+
+public class CoreTest {
+    @Test
+    public void ok() {
+        System.out.println("hello from CoreTest.");
+    }
+}
diff --git a/subprojects/docs/src/samples/testing/testReport/readme.xml b/subprojects/docs/src/samples/testing/testReport/readme.xml
new file mode 100644
index 0000000..113e938
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/testReport/readme.xml
@@ -0,0 +1,5 @@
+<sample>
+    <para>Generates an HTML test report that includes the test results from all subprojects.</para>
+</sample>
+
+
diff --git a/subprojects/docs/src/samples/testing/testReport/settings.gradle b/subprojects/docs/src/samples/testing/testReport/settings.gradle
new file mode 100644
index 0000000..688e2e7
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/testReport/settings.gradle
@@ -0,0 +1,2 @@
+include 'util'
+include 'core'
diff --git a/subprojects/docs/src/samples/testing/testReport/util/src/test/java/org/gradle/sample/UtilTest.java b/subprojects/docs/src/samples/testing/testReport/util/src/test/java/org/gradle/sample/UtilTest.java
new file mode 100644
index 0000000..4e414fc
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/testReport/util/src/test/java/org/gradle/sample/UtilTest.java
@@ -0,0 +1,11 @@
+package org.gradle.sample;
+
+import org.junit.Test;
+import java.lang.System;
+
+public class UtilTest {
+    @Test
+    public void ok() {
+        System.out.println("hello from UtilTest.");
+    }
+}
diff --git a/subprojects/docs/src/samples/testng/java-jdk14-passing/build.gradle b/subprojects/docs/src/samples/testing/testng/java-jdk14-passing/build.gradle
similarity index 100%
rename from subprojects/docs/src/samples/testng/java-jdk14-passing/build.gradle
rename to subprojects/docs/src/samples/testing/testng/java-jdk14-passing/build.gradle
diff --git a/subprojects/docs/src/samples/testng/java-jdk14-passing/src/main/java/org/gradle/Ok.java b/subprojects/docs/src/samples/testing/testng/java-jdk14-passing/src/main/java/org/gradle/Ok.java
similarity index 100%
rename from subprojects/docs/src/samples/testng/java-jdk14-passing/src/main/java/org/gradle/Ok.java
rename to subprojects/docs/src/samples/testing/testng/java-jdk14-passing/src/main/java/org/gradle/Ok.java
diff --git a/subprojects/docs/src/samples/testng/java-jdk14-passing/src/test/java/org/gradle/OkTest.java b/subprojects/docs/src/samples/testing/testng/java-jdk14-passing/src/test/java/org/gradle/OkTest.java
similarity index 100%
rename from subprojects/docs/src/samples/testng/java-jdk14-passing/src/test/java/org/gradle/OkTest.java
rename to subprojects/docs/src/samples/testing/testng/java-jdk14-passing/src/test/java/org/gradle/OkTest.java
diff --git a/subprojects/docs/src/samples/testing/testng/java-jdk15-passing/build.gradle b/subprojects/docs/src/samples/testing/testng/java-jdk15-passing/build.gradle
new file mode 100644
index 0000000..bdfa690
--- /dev/null
+++ b/subprojects/docs/src/samples/testing/testng/java-jdk15-passing/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'java'
+
+sourceCompatibility=1.5
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile 'org.testng:testng:6.3.1'
+}
+
+test {
+   useTestNG(){
+       useDefaultListeners = true
+   }
+}
diff --git a/subprojects/docs/src/samples/testng/java-jdk15-passing/src/main/java/org/gradle/Ok.java b/subprojects/docs/src/samples/testing/testng/java-jdk15-passing/src/main/java/org/gradle/Ok.java
similarity index 100%
rename from subprojects/docs/src/samples/testng/java-jdk15-passing/src/main/java/org/gradle/Ok.java
rename to subprojects/docs/src/samples/testing/testng/java-jdk15-passing/src/main/java/org/gradle/Ok.java
diff --git a/subprojects/docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/AbstractTest.java b/subprojects/docs/src/samples/testing/testng/java-jdk15-passing/src/test/java/org/gradle/AbstractTest.java
similarity index 100%
rename from subprojects/docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/AbstractTest.java
rename to subprojects/docs/src/samples/testing/testng/java-jdk15-passing/src/test/java/org/gradle/AbstractTest.java
diff --git a/subprojects/docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/ConcreteTest.java b/subprojects/docs/src/samples/testing/testng/java-jdk15-passing/src/test/java/org/gradle/ConcreteTest.java
similarity index 100%
rename from subprojects/docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/ConcreteTest.java
rename to subprojects/docs/src/samples/testing/testng/java-jdk15-passing/src/test/java/org/gradle/ConcreteTest.java
diff --git a/subprojects/docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/OkTest.java b/subprojects/docs/src/samples/testing/testng/java-jdk15-passing/src/test/java/org/gradle/OkTest.java
similarity index 100%
rename from subprojects/docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/OkTest.java
rename to subprojects/docs/src/samples/testing/testng/java-jdk15-passing/src/test/java/org/gradle/OkTest.java
diff --git a/subprojects/docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/SuiteCleanup.java b/subprojects/docs/src/samples/testing/testng/java-jdk15-passing/src/test/java/org/gradle/SuiteCleanup.java
similarity index 100%
rename from subprojects/docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/SuiteCleanup.java
rename to subprojects/docs/src/samples/testing/testng/java-jdk15-passing/src/test/java/org/gradle/SuiteCleanup.java
diff --git a/subprojects/docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/SuiteSetup.java b/subprojects/docs/src/samples/testing/testng/java-jdk15-passing/src/test/java/org/gradle/SuiteSetup.java
similarity index 100%
rename from subprojects/docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/SuiteSetup.java
rename to subprojects/docs/src/samples/testing/testng/java-jdk15-passing/src/test/java/org/gradle/SuiteSetup.java
diff --git a/subprojects/docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/TestCleanup.java b/subprojects/docs/src/samples/testing/testng/java-jdk15-passing/src/test/java/org/gradle/TestCleanup.java
similarity index 100%
rename from subprojects/docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/TestCleanup.java
rename to subprojects/docs/src/samples/testing/testng/java-jdk15-passing/src/test/java/org/gradle/TestCleanup.java
diff --git a/subprojects/docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/TestSetup.java b/subprojects/docs/src/samples/testing/testng/java-jdk15-passing/src/test/java/org/gradle/TestSetup.java
similarity index 100%
rename from subprojects/docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/TestSetup.java
rename to subprojects/docs/src/samples/testing/testng/java-jdk15-passing/src/test/java/org/gradle/TestSetup.java
diff --git a/subprojects/docs/src/samples/testng/suitexmlbuilder/build.gradle b/subprojects/docs/src/samples/testing/testng/suitexmlbuilder/build.gradle
similarity index 100%
rename from subprojects/docs/src/samples/testng/suitexmlbuilder/build.gradle
rename to subprojects/docs/src/samples/testing/testng/suitexmlbuilder/build.gradle
diff --git a/subprojects/docs/src/samples/testng/suitexmlbuilder/src/main/java/org/gradle/testng/User.java b/subprojects/docs/src/samples/testing/testng/suitexmlbuilder/src/main/java/org/gradle/testng/User.java
similarity index 100%
rename from subprojects/docs/src/samples/testng/suitexmlbuilder/src/main/java/org/gradle/testng/User.java
rename to subprojects/docs/src/samples/testing/testng/suitexmlbuilder/src/main/java/org/gradle/testng/User.java
diff --git a/subprojects/docs/src/samples/testng/suitexmlbuilder/src/main/java/org/gradle/testng/UserImpl.java b/subprojects/docs/src/samples/testing/testng/suitexmlbuilder/src/main/java/org/gradle/testng/UserImpl.java
similarity index 100%
rename from subprojects/docs/src/samples/testng/suitexmlbuilder/src/main/java/org/gradle/testng/UserImpl.java
rename to subprojects/docs/src/samples/testing/testng/suitexmlbuilder/src/main/java/org/gradle/testng/UserImpl.java
diff --git a/subprojects/docs/src/samples/testng/suitexmlbuilder/src/test/java/org/gradle/testng/UserImplTest.java b/subprojects/docs/src/samples/testing/testng/suitexmlbuilder/src/test/java/org/gradle/testng/UserImplTest.java
similarity index 100%
rename from subprojects/docs/src/samples/testng/suitexmlbuilder/src/test/java/org/gradle/testng/UserImplTest.java
rename to subprojects/docs/src/samples/testing/testng/suitexmlbuilder/src/test/java/org/gradle/testng/UserImplTest.java
diff --git a/subprojects/docs/src/samples/testng/java-jdk15-passing/build.gradle b/subprojects/docs/src/samples/testng/java-jdk15-passing/build.gradle
deleted file mode 100644
index 9839ede..0000000
--- a/subprojects/docs/src/samples/testng/java-jdk15-passing/build.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-apply plugin: 'java'
-
-sourceCompatibility=1.5
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    testCompile 'org.testng:testng:6.3.1'
-}
-
-test {
-   useTestNG() 
-}
diff --git a/subprojects/docs/src/samples/toolingApi/eclipse/build.gradle b/subprojects/docs/src/samples/toolingApi/eclipse/build.gradle
index 78911c0..ede7af4 100644
--- a/subprojects/docs/src/samples/toolingApi/eclipse/build.gradle
+++ b/subprojects/docs/src/samples/toolingApi/eclipse/build.gradle
@@ -13,7 +13,7 @@ repositories {
 dependencies {
     compile "org.gradle:gradle-tooling-api:${toolingApiVersion}"
     // Need an SLF4J implementation at runtime
-    runtime 'org.slf4j:slf4j-simple:1.6.6'
+    runtime 'org.slf4j:slf4j-simple:1.7.2'
 }
 
 mainClassName = 'org.gradle.sample.Main'
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/toolingApi/idea/build.gradle b/subprojects/docs/src/samples/toolingApi/idea/build.gradle
index 053888e..fbfa05a 100644
--- a/subprojects/docs/src/samples/toolingApi/idea/build.gradle
+++ b/subprojects/docs/src/samples/toolingApi/idea/build.gradle
@@ -13,7 +13,7 @@ repositories {
 dependencies {
     compile "org.gradle:gradle-tooling-api:${toolingApiVersion}"
     // Need an SLF4J implementation at runtime
-    runtime 'org.slf4j:slf4j-simple:1.6.6'
+    runtime 'org.slf4j:slf4j-simple:1.7.2'
 }
 
 mainClassName = 'org.gradle.sample.Main'
diff --git a/subprojects/docs/src/samples/toolingApi/model/build.gradle b/subprojects/docs/src/samples/toolingApi/model/build.gradle
index 053888e..fbfa05a 100644
--- a/subprojects/docs/src/samples/toolingApi/model/build.gradle
+++ b/subprojects/docs/src/samples/toolingApi/model/build.gradle
@@ -13,7 +13,7 @@ repositories {
 dependencies {
     compile "org.gradle:gradle-tooling-api:${toolingApiVersion}"
     // Need an SLF4J implementation at runtime
-    runtime 'org.slf4j:slf4j-simple:1.6.6'
+    runtime 'org.slf4j:slf4j-simple:1.7.2'
 }
 
 mainClassName = 'org.gradle.sample.Main'
diff --git a/subprojects/docs/src/samples/toolingApi/runBuild/build.gradle b/subprojects/docs/src/samples/toolingApi/runBuild/build.gradle
index 053888e..fbfa05a 100644
--- a/subprojects/docs/src/samples/toolingApi/runBuild/build.gradle
+++ b/subprojects/docs/src/samples/toolingApi/runBuild/build.gradle
@@ -13,7 +13,7 @@ repositories {
 dependencies {
     compile "org.gradle:gradle-tooling-api:${toolingApiVersion}"
     // Need an SLF4J implementation at runtime
-    runtime 'org.slf4j:slf4j-simple:1.6.6'
+    runtime 'org.slf4j:slf4j-simple:1.7.2'
 }
 
 mainClassName = 'org.gradle.sample.Main'
diff --git a/subprojects/docs/src/samples/userguide/artifacts/defineRepository/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/defineRepository/build.gradle
index ece7988..55b068a 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/defineRepository/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/defineRepository/build.gradle
@@ -102,6 +102,19 @@ repositories {
 }
 //END SNIPPET ivy-repo-with-pattern-layout
 
+//START SNIPPET ivy-repo-with-m2compatible-layout
+repositories {
+    ivy {
+        url "http://repo.mycompany.com/repo"
+        layout 'pattern', {
+            artifact "[organisation]/[module]/[revision]/[artifact].[ext]"
+            ivy "[organisation]/[module]/[revision]/ivy.xml"
+            m2compatible = true
+        }
+    }
+}
+//END SNIPPET ivy-repo-with-m2compatible-layout
+
 //START SNIPPET ivy-repo-with-custom-pattern
 repositories {
     ivy {
diff --git a/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle
index 71e8fbd..37722a8 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle
@@ -59,14 +59,14 @@ dependencies {
 
 //START SNIPPET artifact-only
 dependencies {
-	runtime "org.groovy:groovy:1.8.8 at jar"
-    runtime group: 'org.groovy', name: 'groovy', version: '1.8.8', ext: 'jar'
+	runtime "org.groovy:groovy:2.0.5 at jar"
+    runtime group: 'org.groovy', name: 'groovy', version: '2.0.5', ext: 'jar'
 }
 //END SNIPPET artifact-only
 
 //START SNIPPET client-modules
 dependencies {
-    runtime module("org.codehaus.groovy:groovy-all:1.8.8") {
+    runtime module("org.codehaus.groovy:groovy-all:2.0.5") {
         dependency("commons-cli:commons-cli:1.0") {
             transitive = false
         }
@@ -85,7 +85,7 @@ dependencies {
 //END SNIPPET file-dependencies
 
 //START SNIPPET list-grouping
-List groovy = ["org.codehaus.groovy:groovy-all:1.8.8 at jar",
+List groovy = ["org.codehaus.groovy:groovy-all:2.0.5 at jar",
                "commons-cli:commons-cli:1.0 at jar",
                "org.apache.ant:ant:1.8.4 at jar"]
 List hibernate = ['org.hibernate:hibernate:3.0.5 at jar', 'somegroup:someorg:1.0 at jar']
diff --git a/subprojects/docs/src/samples/userguide/artifacts/maven/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/maven/build.gradle
index 5c34067..ba181c5 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/maven/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/maven/build.gradle
@@ -55,7 +55,7 @@ repositories {
 }
 
 dependencies {
-    deployerJars "org.apache.maven.wagon:wagon-ssh:1.0-beta-2"
+    deployerJars "org.apache.maven.wagon:wagon-ssh:2.2"
 }
 
 uploadArchives {
diff --git a/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle
index 266f993..0c76d42 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle
@@ -6,6 +6,42 @@ configurations.all {
 
 //START SNIPPET changing-module-cache-control
 configurations.all {
-    resolutionStrategy.cacheChangingModulesFor 30, 'days'
+    resolutionStrategy.cacheChangingModulesFor 4, 'hours'
 }
 //END SNIPPET changing-module-cache-control
+
+//START SNIPPET releasable-unit
+configurations.all {
+    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
+        if (details.requested.group == 'org.gradle') {
+            details.useVersion '1.4'
+        }
+    }
+}
+//END SNIPPET releasable-unit
+
+//START SNIPPET custom-versioning-scheme
+configurations.all {
+    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
+        if (details.requested.version == 'default') {
+            def version = findDefaultVersionInCatalog(details.requested.group, details.requested.name)
+            details.useVersion version
+        }
+    }
+}
+
+def findDefaultVersionInCatalog(String group, String name) {
+    //some custom logic that resolves the default version into a specific version
+}
+//END SNIPPET custom-versioning-scheme
+
+//START SNIPPET blacklisting_version
+configurations.all {
+    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
+        if (details.requested.group == 'org.cool.software' && details.requested.name == 'cool-library' && details.requested.version == '1.2') {
+            //prefer different version which contains some necessary fixes
+            details.useVersion '1.2.1'
+        }
+    }
+}
+//END SNIPPET blacklisting_version
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/groovy/groovyDependency/build.gradle b/subprojects/docs/src/samples/userguide/groovy/groovyDependency/build.gradle
new file mode 100644
index 0000000..ab87fe3
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/groovy/groovyDependency/build.gradle
@@ -0,0 +1,23 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
+
+// START SNIPPET groovy-test-dependency
+dependencies {
+    testCompile "org.codehaus.groovy:groovy-all:2.0.5"
+}
+// END SNIPPET groovy-test-dependency
+
+// START SNIPPET groovy-configuration
+dependencies {
+    groovy "org.codehaus.groovy:groovy-all:2.0.5"
+}
+// END SNIPPET groovy-configuration
+
+// START SNIPPET bundled-groovy-dependency
+dependencies {
+    compile localGroovy()
+}
+// END SNIPPET bundled-groovy-dependency
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/java/sourceSets/build.gradle b/subprojects/docs/src/samples/userguide/java/sourceSets/build.gradle
index bbe4d67..d0034ba 100644
--- a/subprojects/docs/src/samples/userguide/java/sourceSets/build.gradle
+++ b/subprojects/docs/src/samples/userguide/java/sourceSets/build.gradle
@@ -39,7 +39,7 @@ sourceSets {
 }
 
 dependencies {
-    intTestCompile 'junit:junit:4.8.2'
+    intTestCompile 'junit:junit:4.11'
     intTestRuntime 'org.ow2.asm:asm-all:4.0'
 }
 // END SNIPPET source-set-dependencies
diff --git a/subprojects/docs/src/samples/userguide/javaLibraryDistribution/build.gradle b/subprojects/docs/src/samples/userguide/javaLibraryDistribution/build.gradle
new file mode 100755
index 0000000..8f2179e
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/javaLibraryDistribution/build.gradle
@@ -0,0 +1,28 @@
+// START SNIPPET use-plugin
+apply plugin: 'java-library-distribution'
+// END SNIPPET use-plugin
+
+version = '1.0.0'
+
+// START SNIPPET name-conf
+distribution {
+    name = 'my-name'
+}
+// END SNIPPET name-conf
+
+// START SNIPPET custom-distZip
+distZip {
+    from('oneFile') //Copy to the root of the distribution
+    from('secondFile') {
+        into('dist') // Copy to the dist directory
+    }
+}
+// END SNIPPET custom-distZip
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile 'commons-collections:commons-collections:3.2.1'
+}
diff --git a/subprojects/docs/src/samples/userguide/javaLibraryDistribution/readme.xml b/subprojects/docs/src/samples/userguide/javaLibraryDistribution/readme.xml
new file mode 100755
index 0000000..756b975
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/javaLibraryDistribution/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>A project which uses the Java library distribution plugin</para>
+</sample>
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/java/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/java/build.gradle
index 0318cc3..8189328 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/dependencies/java/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/java/build.gradle
@@ -6,7 +6,7 @@ subprojects {
         mavenCentral()
     }
     dependencies {
-        testCompile "junit:junit:4.8.2"
+        testCompile "junit:junit:4.11"
     }
 }
 
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/build.gradle
index 2d8c71d..d6c13df 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/build.gradle
@@ -26,6 +26,6 @@ project(':services:personService') {
     dependencies {
         compile project(':shared')
         compile project(path: ':api', configuration: 'spi')
-        testCompile "junit:junit:4.8.2", project(':api')
+        testCompile "junit:junit:4.11", project(':api')
     }
 }
diff --git a/subprojects/docs/src/samples/userguide/scala/scalaDependency/build.gradle b/subprojects/docs/src/samples/userguide/scala/scalaDependency/build.gradle
new file mode 100644
index 0000000..ec27293
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/scala/scalaDependency/build.gradle
@@ -0,0 +1,11 @@
+apply plugin: "scala"
+
+repositories {
+    mavenCentral()
+}
+
+// START SNIPPET scala-test-dependency
+dependencies {
+    testCompile "org.scala-lang:scala-library:2.9.2"
+}
+// END SNIPPET scala-test-dependency
diff --git a/subprojects/docs/src/samples/userguide/tutorial/groovy/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/groovy/build.gradle
index ebca2d2..40198d6 100644
--- a/subprojects/docs/src/samples/userguide/tutorial/groovy/build.gradle
+++ b/subprojects/docs/src/samples/userguide/tutorial/groovy/build.gradle
@@ -48,7 +48,7 @@ repositories({ println "in a closure" })
 // START SNIPPET closureDelegates
 dependencies {
     assert delegate == project.dependencies
-    compile('junit:junit:4.8.2')
-    delegate.compile('junit:junit:4.8.2')
+    compile('junit:junit:4.11')
+    delegate.compile('junit:junit:4.11')
 }
 // END SNIPPET closureDelegates
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/tutorial/projectReports/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/projectReports/build.gradle
index 24538b2..419149a 100644
--- a/subprojects/docs/src/samples/userguide/tutorial/projectReports/build.gradle
+++ b/subprojects/docs/src/samples/userguide/tutorial/projectReports/build.gradle
@@ -43,6 +43,7 @@ dists {
 subprojects {
     configurations {
         compile
+        testCompile
     }
     repositories {
         mavenCentral()
@@ -63,7 +64,8 @@ project(':api') {
 description = 'The shared API for the application'
 // END SNIPPET project-description
     dependencies {
-        compile "org.codehaus.groovy:groovy-all:1.8.8"
+        compile "org.codehaus.groovy:groovy-all:2.0.5"
+        testCompile "junit:junit:4.11"
     }
 }
 
diff --git a/subprojects/docs/src/samples/userguideOutput/dependencyInsightReport.out b/subprojects/docs/src/samples/userguideOutput/dependencyInsightReport.out
index 78c8158..117017d 100644
--- a/subprojects/docs/src/samples/userguideOutput/dependencyInsightReport.out
+++ b/subprojects/docs/src/samples/userguideOutput/dependencyInsightReport.out
@@ -1,3 +1,3 @@
-org.codehaus.groovy:groovy-all:1.8.8
+org.codehaus.groovy:groovy-all:2.0.5
 \--- projectReports:api:1.0-SNAPSHOT
      \--- compile
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/dependencyListReport.out b/subprojects/docs/src/samples/userguideOutput/dependencyListReport.out
index ea04b05..94f2c4f 100644
--- a/subprojects/docs/src/samples/userguideOutput/dependencyListReport.out
+++ b/subprojects/docs/src/samples/userguideOutput/dependencyListReport.out
@@ -10,7 +10,11 @@ Project :api - The shared API for the application
 ------------------------------------------------------------
 
 compile
-\--- org.codehaus.groovy:groovy-all:1.8.8
+\--- org.codehaus.groovy:groovy-all:2.0.5
+
+testCompile
+\--- junit:junit:4.11
+     \--- org.hamcrest:hamcrest-core:1.3
 
 ------------------------------------------------------------
 Project :webapp - The Web application implementation
@@ -18,5 +22,8 @@ Project :webapp - The Web application implementation
 
 compile
 +--- projectReports:api:1.0-SNAPSHOT
-|    \--- org.codehaus.groovy:groovy-all:1.8.8
-\--- commons-io:commons-io:1.2
\ No newline at end of file
+|    \--- org.codehaus.groovy:groovy-all:2.0.5
+\--- commons-io:commons-io:1.2
+
+testCompile
+No dependencies
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/dependencyListReportFiltered.out b/subprojects/docs/src/samples/userguideOutput/dependencyListReportFiltered.out
new file mode 100644
index 0000000..e105e73
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/dependencyListReportFiltered.out
@@ -0,0 +1,8 @@
+
+------------------------------------------------------------
+Project :api - The shared API for the application
+------------------------------------------------------------
+
+testCompile
+\--- junit:junit:4.11
+     \--- org.hamcrest:hamcrest-core:1.3
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishLifecycle.out b/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishLifecycle.out
new file mode 100644
index 0000000..5679f05
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishLifecycle.out
@@ -0,0 +1,16 @@
+:generateIvyModuleDescriptor
+:subproject:compileJava
+:subproject:processResources UP-TO-DATE
+:subproject:classes
+:subproject:jar
+:compileJava
+:processResources UP-TO-DATE
+:classes
+:jar
+:sourceJar
+:publishIvyPublicationToIvyRepository
+:publish
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishSingle.out b/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishSingle.out
new file mode 100644
index 0000000..aa9bd03
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishSingle.out
@@ -0,0 +1,15 @@
+:generateIvyModuleDescriptor
+:subproject:compileJava
+:subproject:processResources UP-TO-DATE
+:subproject:classes
+:subproject:jar
+:compileJava
+:processResources UP-TO-DATE
+:classes
+:jar
+:sourceJar
+:publishIvyPublicationToIvyRepository
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/docs/src/samples/userguideOutput/publishingMavenPublishLocal.out b/subprojects/docs/src/samples/userguideOutput/publishingMavenPublishLocal.out
new file mode 100644
index 0000000..1f0686a
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/publishingMavenPublishLocal.out
@@ -0,0 +1,10 @@
+:compileJava
+:processResources UP-TO-DATE
+:classes
+:jar
+:publishMavenPublicationToMavenLocal
+:publishToMavenLocal
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/docs/src/samples/userguideOutput/publishingMavenPublishMinimal.out b/subprojects/docs/src/samples/userguideOutput/publishingMavenPublishMinimal.out
new file mode 100644
index 0000000..7b3f6f0
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/publishingMavenPublishMinimal.out
@@ -0,0 +1,10 @@
+:compileJava
+:processResources UP-TO-DATE
+:classes
+:jar
+:publishMavenPublicationToMavenRepository
+:publish
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/docs/src/samples/webApplication/customised/build.gradle b/subprojects/docs/src/samples/webApplication/customised/build.gradle
index 675c11d..fdce6c5 100644
--- a/subprojects/docs/src/samples/webApplication/customised/build.gradle
+++ b/subprojects/docs/src/samples/webApplication/customised/build.gradle
@@ -26,7 +26,7 @@ dependencies {
     }
     runtime ":runtime:1.0"
     providedRuntime ":providedRuntime:1.0 at jar"
-    testCompile "junit:junit:4.8.2"
+    testCompile "junit:junit:4.11"
     moreLibs ":otherLib:1.0"
 }
 
diff --git a/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/FunctionalReleaseNotesTest.groovy b/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/FunctionalReleaseNotesTest.groovy
new file mode 100644
index 0000000..9b313bb
--- /dev/null
+++ b/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/FunctionalReleaseNotesTest.groovy
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.docs.releasenotes
+
+import geb.Browser
+import geb.Configuration
+import geb.spock.GebReportingSpec
+import groovy.json.JsonSlurper
+import org.gradle.util.GradleVersion
+import org.openqa.selenium.htmlunit.HtmlUnitDriver
+import spock.lang.IgnoreIf
+import spock.lang.Shared
+
+/**
+ * These tests actually open the release notes in a browser and test the JS.
+ */
+ at IgnoreIf({ !canReachServices() })
+class FunctionalReleaseNotesTest extends GebReportingSpec {
+
+    static private final String FIXED_ISSUES_URL = "http://services.gradle.org/fixed-issues/${GradleVersion.current().versionBase}"
+    static private final String KNOWN_ISSUES_URL = "http://services.gradle.org/known-issues/${GradleVersion.current().versionBase}"
+
+    private String version = GradleVersion.current().versionBase
+
+    static boolean canReachServices() {
+        try {
+            HttpURLConnection connection = FIXED_ISSUES_URL.toURL().openConnection() as HttpURLConnection
+            connection.requestMethod = "HEAD"
+            connection.connect()
+            connection.responseCode == 200
+        } catch (ignored) {
+            false
+        }
+    }
+
+    @Shared url = new ReleaseNotesTestContext().renderedFile.toURL().toString()
+
+    def setup() {
+        to ReleaseNotesPage
+    }
+
+    @Override
+    ReleaseNotesPage getPage() {
+        browser.page as ReleaseNotesPage
+    }
+
+    @Override
+    Browser createBrowser() {
+        new Browser(driver: new HtmlUnitDriver(true), new Configuration(reportsDir: new File("build/geb-reports")))
+    }
+
+    List<Map> fixedIssues() {
+        new JsonSlurper().parseText(new URL(FIXED_ISSUES_URL).text) as List<Map>
+    }
+
+    List<Map> knownIssues() {
+        new JsonSlurper().parseText(new URL(KNOWN_ISSUES_URL).text) as List<Map>
+    }
+
+    def "has fixed issues"() {
+        when:
+        def fixed = fixedIssues()
+        def numFixedIssues = fixed.size()
+
+        then:
+        waitFor { page.fixedIssuesParagraph.text() == "$numFixedIssues issues have been fixed in Gradle $version." }
+        if (numFixedIssues == 0) {
+            return
+        }
+
+        page.fixedIssuesListItems.size() == numFixedIssues
+        fixed.eachWithIndex { json, i ->
+            def issue = page.fixedIssuesListItems[i]
+            assert issue.text() == "[$json.key] - ${json.summary.trim()}"
+            assert issue.find("a").attr("href") == json.link
+        }
+    }
+
+    def "has known issues"() {
+        when:
+        def knownIssues = knownIssues()
+
+        then:
+        if (knownIssues.size() == 0) {
+            waitFor { page.knownIssuesParagraph.text() == "There are no known issues of Gradle ${version} at this time." }
+            return
+        } else {
+            waitFor { page.knownIssuesParagraph.text() == "There are ${knownIssues.size()} known issues of Gradle $version." }
+        }
+
+        page.knownIssuesListItems.size() == knownIssues.size()
+        knownIssues.eachWithIndex { json, i ->
+            def issue = page.knownIssuesListItems[i]
+            assert issue.text() == "[$json.key] - ${json.summary.trim()}"
+            assert issue.find("a").attr("href") == json.link
+        }
+    }
+
+    def cleanupSpec() {
+        browser.quit()
+    }
+}
diff --git a/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/ReleaseNotesPage.groovy b/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/ReleaseNotesPage.groovy
new file mode 100644
index 0000000..3873f3c
--- /dev/null
+++ b/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/ReleaseNotesPage.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.docs.releasenotes
+
+import geb.Page
+
+class ReleaseNotesPage extends Page {
+
+    static url = new ReleaseNotesTestContext().renderedFile.toURL().toString()
+
+    static content = {
+        fixedIssuesHeading { $("#fixed-issues") }
+        fixedIssuesParagraph { fixedIssuesHeading.next() }
+        fixedIssuesListItems { $("ul#fixed-issues-list li") }
+        knownIssuesHeading { $("#known-issues") }
+        knownIssuesParagraph { knownIssuesHeading.next("p").next("p") }
+        knownIssuesListItems { $("ul#known-issues-list li") }
+
+    }
+}
diff --git a/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/ReleaseNotesTestContext.groovy b/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/ReleaseNotesTestContext.groovy
new file mode 100644
index 0000000..cbc3615
--- /dev/null
+++ b/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/ReleaseNotesTestContext.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.docs.releasenotes
+
+class ReleaseNotesTestContext {
+
+    private static File getSysPropFile(String property) {
+        def value = System.getProperty(property)
+        assert value != null : "System property '$property' is not set"
+        def file = new File(value)
+        assert file.file : "File '$file' (from system property '$property') does not exist"
+        file
+    }
+
+    File getSourceFile() {
+        getSysPropFile("org.gradle.docs.releasenotes.source")
+    }
+
+    File getRenderedFile() {
+        getSysPropFile("org.gradle.docs.releasenotes.rendered")
+    }
+
+}
diff --git a/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/StaticReleaseNotesTest.groovy b/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/StaticReleaseNotesTest.groovy
new file mode 100644
index 0000000..07edd66
--- /dev/null
+++ b/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/StaticReleaseNotesTest.groovy
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.docs.releasenotes
+
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
+import spock.lang.Shared
+import spock.lang.Specification
+
+class StaticReleaseNotesTest extends Specification {
+
+    @Shared File sourceFile
+    @Shared String sourceText
+
+    @Shared File renderedFile
+    @Shared Document renderedDocument
+    @Shared String renderedText
+
+    def setupSpec() {
+        def context = new ReleaseNotesTestContext()
+        sourceFile = context.sourceFile
+        sourceText = sourceFile.getText("utf-8")
+        renderedFile = context.renderedFile
+        renderedText = renderedFile.getText("utf-8")
+        renderedDocument = Jsoup.parse(renderedText)
+    }
+
+    def "has fixed issues holder"() {
+        expect:
+        !renderedDocument.body().select("h2#fixed-issues").empty
+    }
+
+    def "line length check"() {
+        when:
+        def maxLineLength = 220
+        def tooLongLines = [:] // key is line num, value is line
+        sourceText.eachLine { String line, int num ->
+            if (line.size() > maxLineLength) {
+                tooLongLines[num] = line
+            }
+        }
+
+        then:
+        tooLongLines.isEmpty()
+    }
+
+    def "no duplicate ids"() {
+        when:
+        def groupedElements = renderedDocument.body().allElements.findAll { it.id() }.groupBy { it.id() }
+        def duplicateIds = groupedElements.keySet().findAll { groupedElements[it].size() > 1 }
+
+        then:
+        duplicateIds.empty
+    }
+
+    def "no broken internal links"() {
+        when:
+        def brokenAnchorLinks = []
+        def links = renderedDocument.select("a")
+        def ids = renderedDocument.allElements.findAll { it.id() }*.id()
+        def anchors = links.findAll { it.attr("name") }*.attr("name")
+
+        links.each {
+            def href = it.attr("href")
+            if (href.startsWith("#")) {
+                def target = href[1..-1]
+                if (!ids.contains(target) && !anchors.contains(target)) {
+                    brokenAnchorLinks << target
+                }
+            }
+        }
+
+        then:
+        brokenAnchorLinks.empty
+    }
+}
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/EarPlugin.java b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/EarPlugin.java
index ae8a228..126d317 100644
--- a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/EarPlugin.java
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/EarPlugin.java
@@ -23,15 +23,15 @@ import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.file.FileCollection;
-import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
+import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet;
-import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.plugins.BasePlugin;
 import org.gradle.api.plugins.JavaPlugin;
 import org.gradle.api.plugins.JavaPluginConvention;
 import org.gradle.api.plugins.PluginContainer;
 import org.gradle.api.tasks.SourceSet;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.plugins.ear.descriptor.DeploymentDescriptor;
 
 import javax.inject.Inject;
@@ -44,23 +44,25 @@ import java.util.concurrent.Callable;
  *
  * @author David Gileadi, Hans Dockter
  */
-public class EarPlugin implements Plugin<ProjectInternal> {
+public class EarPlugin implements Plugin<Project> {
 
     public static final String EAR_TASK_NAME = "ear";
 
     public static final String DEPLOY_CONFIGURATION_NAME = "deploy";
     public static final String EARLIB_CONFIGURATION_NAME = "earlib";
     private final Instantiator instantiator;
+    private final FileResolver fileResolver;
 
     @Inject
-    public EarPlugin(Instantiator instantiator) {
+    public EarPlugin(Instantiator instantiator, FileResolver fileResolver) {
         this.instantiator = instantiator;
+        this.fileResolver = fileResolver;
     }
 
-    public void apply(final ProjectInternal project) {
+    public void apply(final Project project) {
         project.getPlugins().apply(BasePlugin.class);
 
-        final EarPluginConvention earPluginConvention = instantiator.newInstance(EarPluginConvention.class, project.getFileResolver());
+        final EarPluginConvention earPluginConvention = instantiator.newInstance(EarPluginConvention.class, fileResolver);
         project.getConvention().getPlugins().put("ear", earPluginConvention);
         earPluginConvention.setLibDirName("lib");
         earPluginConvention.setAppDirName("src/main/application");
@@ -76,7 +78,7 @@ public class EarPlugin implements Plugin<ProjectInternal> {
         configureWithNoJavaPluginApplied(project, earPluginConvention);
     }
 
-    private void configureWithNoJavaPluginApplied(final ProjectInternal project, final EarPluginConvention earPluginConvention) {
+    private void configureWithNoJavaPluginApplied(final Project project, final EarPluginConvention earPluginConvention) {
         project.getTasks().withType(Ear.class, new Action<Ear>() {
             public void execute(final Ear task) {
                 task.from(new Callable<FileCollection>() {
@@ -92,7 +94,7 @@ public class EarPlugin implements Plugin<ProjectInternal> {
         });
     }
 
-    private void configureWithJavaPluginApplied(final ProjectInternal project, final EarPluginConvention earPluginConvention, PluginContainer plugins) {
+    private void configureWithJavaPluginApplied(final Project project, final EarPluginConvention earPluginConvention, PluginContainer plugins) {
         plugins.withType(JavaPlugin.class, new Action<JavaPlugin>() {
             public void execute(JavaPlugin javaPlugin) {
                 final JavaPluginConvention javaPluginConvention = project.getConvention().findPlugin(JavaPluginConvention.class);
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptor.groovy b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptor.groovy
index 1dc74fc..825e78f 100644
--- a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptor.groovy
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptor.groovy
@@ -22,7 +22,7 @@ import org.gradle.api.XmlProvider
 import org.gradle.api.internal.DomNode
 import org.gradle.api.internal.ErroringAction
 import org.gradle.api.internal.IoActions
-import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.api.internal.file.FileResolver
 import org.gradle.plugins.ear.descriptor.DeploymentDescriptor
 import org.gradle.plugins.ear.descriptor.EarModule
diff --git a/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/EarPluginTest.groovy b/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/EarPluginTest.groovy
index c374a1d..399878d 100644
--- a/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/EarPluginTest.groovy
+++ b/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/EarPluginTest.groovy
@@ -33,14 +33,12 @@ import static org.gradle.util.TextUtil.toPlatformLineSeparators
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 import org.gradle.api.plugins.JavaBasePlugin
-import org.gradle.internal.reflect.Instantiator
 
 /**
  * @author David Gileadi
  */
 class EarPluginTest {
     private ProjectInternal project
-    private EarPlugin earPlugin
     private static final String TEST_APP_XML = toPlatformLineSeparators('<?xml version="1.0" encoding="UTF-8"?>\n' +
         '<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:application="http://java.sun.com/xml/ns/javaee/application_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_5.xsd" version="5">\n' +
         '  <display-name>Application</display-name>\n' +
@@ -59,18 +57,17 @@ class EarPluginTest {
     @Before
     public void setUp() {
         project = HelperUtil.createRootProject()
-        earPlugin = new EarPlugin(project.services.get(Instantiator))
     }
 
     @Test public void appliesBasePluginAndAddsConvention() {
-        earPlugin.apply(project)
-        
+        project.plugins.apply(EarPlugin)
+
         assertTrue(project.getPlugins().hasPlugin(BasePlugin));
         assertThat(project.convention.plugins.ear, instanceOf(EarPluginConvention))
     }
     
     @Test public void createsConfigurations() {
-        earPlugin.apply(project)
+        project.plugins.apply(EarPlugin)
 
         def configuration = project.configurations.getByName(EarPlugin.DEPLOY_CONFIGURATION_NAME)
         assertFalse(configuration.visible)
@@ -82,7 +79,7 @@ class EarPluginTest {
     }
 
     @Test public void addsTasks() {
-        earPlugin.apply(project)
+        project.plugins.apply(EarPlugin)
 
         def task = project.tasks[EarPlugin.EAR_TASK_NAME]
         assertThat(task, instanceOf(Ear))
@@ -94,7 +91,7 @@ class EarPluginTest {
 
     @Test public void addsTasksToJavaProject() {
         project.plugins.apply(JavaPlugin.class)
-        earPlugin.apply(project)
+        project.plugins.apply(EarPlugin)
 
         def task = project.tasks[EarPlugin.EAR_TASK_NAME]
         assertThat(task, instanceOf(Ear))
@@ -106,7 +103,7 @@ class EarPluginTest {
     }
 
     @Test public void dependsOnEarlibConfig() {
-        earPlugin.apply(project)
+        project.plugins.apply(EarPlugin)
 
         Project childProject = HelperUtil.createChildProject(project, 'child')
         JavaPlugin javaPlugin = new JavaPlugin()
@@ -121,7 +118,7 @@ class EarPluginTest {
     }
 
     @Test public void appliesMappingsToArchiveTasks() {
-        earPlugin.apply(project)
+        project.plugins.apply(EarPlugin)
 
         def task = project.task(type: Ear, 'customEar')
         assertThat(task.destinationDir, equalTo(project.libsDir))
@@ -129,13 +126,14 @@ class EarPluginTest {
 
     @Test public void worksWithJavaBasePluginAppliedBeforeEarPlugin() {
         project.plugins.apply(JavaBasePlugin.class)
-        earPlugin.apply(project)
+        project.plugins.apply(EarPlugin)
+
         def task = project.task(type: Ear, 'customEar')
         assertThat(task.destinationDir, equalTo(project.libsDir))
     }
 
     @Test public void appliesMappingsToArchiveTasksForJavaProject() {
-        earPlugin.apply(project)
+        project.plugins.apply(EarPlugin)
         project.plugins.apply(JavaPlugin.class)
 
         def task = project.task(type: Ear, 'customEar') {
@@ -147,7 +145,7 @@ class EarPluginTest {
     }
 
     @Test public void addsEarAsPublication() {
-        earPlugin.apply(project)
+        project.plugins.apply(EarPlugin)
 
         Configuration archiveConfiguration = project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION);
         assertThat(archiveConfiguration.getAllArtifacts().size(), equalTo(1));
@@ -155,8 +153,8 @@ class EarPluginTest {
     }
 
     @Test public void replacesWarAsPublication() {
-        earPlugin.apply(project)
-        new WarPlugin().apply(project)
+        project.plugins.apply(EarPlugin)
+        project.plugins.apply(WarPlugin)
 
         Configuration archiveConfiguration = project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION);
         assertThat(archiveConfiguration.getAllArtifacts().size(), equalTo(1));
@@ -164,8 +162,8 @@ class EarPluginTest {
     }
 
     @Test public void replacesJarAsPublication() {
-        earPlugin.apply(project)
-        new JavaPlugin().apply(project)
+        project.plugins.apply(EarPlugin)
+        project.plugins.apply(JavaPlugin)
 
         Configuration archiveConfiguration = project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION);
         assertThat(archiveConfiguration.getAllArtifacts().size(), equalTo(1));
@@ -177,7 +175,7 @@ class EarPluginTest {
         project.file("src/main/application/META-INF/test.txt").createNewFile()
         project.file("src/main/application/test2.txt").createNewFile()
 
-        earPlugin.apply(project)
+        project.plugins.apply(EarPlugin)
 
         execute project.tasks[EarPlugin.EAR_TASK_NAME]
 
@@ -189,7 +187,7 @@ class EarPluginTest {
         project.file("src/main/myapp").mkdirs()
         project.file("src/main/myapp/test.txt").createNewFile()
 
-        earPlugin.apply(project)
+        project.plugins.apply(EarPlugin)
         project.convention.plugins.ear.appDirName = "src/main/myapp"
 
         execute project.tasks[EarPlugin.EAR_TASK_NAME]
@@ -205,7 +203,7 @@ class EarPluginTest {
 
         execute childProject.tasks[BasePlugin.ASSEMBLE_TASK_NAME]
 
-        earPlugin.apply(project)
+        project.plugins.apply(EarPlugin)
         project.convention.plugins.ear.libDirName = "APP-INF/lib"
         project.dependencies {
             earlib project(path: childProject.path, configuration: 'archives')
@@ -217,7 +215,7 @@ class EarPluginTest {
     }
 
     @Test public void supportsGeneratingDeploymentDescriptor() {
-        earPlugin.apply(project)
+        project.plugins.apply(EarPlugin)
         execute project.tasks[EarPlugin.EAR_TASK_NAME]
 
         inEar "META-INF/application.xml"
@@ -227,14 +225,14 @@ class EarPluginTest {
         project.file("src/main/application/META-INF").mkdirs()
         project.file("src/main/application/META-INF/application.xml").text = TEST_APP_XML
 
-        earPlugin.apply(project)
+        project.plugins.apply(EarPlugin)
         execute project.tasks[EarPlugin.EAR_TASK_NAME]
 
         assert inEar("META-INF/application.xml").text == TEST_APP_XML
     }
 
     @Test public void supportsRenamingDeploymentDescriptor() {
-        earPlugin.apply(project)
+        project.plugins.apply(EarPlugin)
         project.convention.plugins.ear.deploymentDescriptor {
             fileName = "myapp.xml"
         }
@@ -247,7 +245,7 @@ class EarPluginTest {
         project.file("src/main/application/META-INF").mkdirs()
         project.file("src/main/application/META-INF/myapp.xml").text = TEST_APP_XML
 
-        earPlugin.apply(project)
+        project.plugins.apply(EarPlugin)
         project.convention.plugins.ear.deploymentDescriptor {
             fileName = "myapp.xml"
         }
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AbstractIdeIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AbstractIdeIntegrationTest.groovy
index 9c79e0d..c729de0 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AbstractIdeIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AbstractIdeIntegrationTest.groovy
@@ -18,8 +18,8 @@
 package org.gradle.plugins.ide
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.ExecutionResult
-import org.gradle.util.TestFile
+import org.gradle.integtests.fixtures.executer.ExecutionResult
+import org.gradle.test.fixtures.file.TestFile
 
 abstract class AbstractIdeIntegrationTest extends AbstractIntegrationTest {
     protected ExecutionResult runTask(taskName, settingsScript = "rootProject.name = 'root'", buildScript) {
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/AbstractEclipseIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/AbstractEclipseIntegrationTest.groovy
index 5d4de05..2595317 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/AbstractEclipseIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/AbstractEclipseIntegrationTest.groovy
@@ -15,8 +15,9 @@
  */
 package org.gradle.plugins.ide.eclipse
 
-import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.integtests.fixtures.executer.ExecutionResult
 import org.gradle.plugins.ide.AbstractIdeIntegrationTest
+import org.gradle.test.fixtures.file.TestFile
 
 class AbstractEclipseIntegrationTest extends AbstractIdeIntegrationTest {
     protected ExecutionResult runEclipseTask(settingsScript = "rootProject.name = 'root'", buildScript) {
@@ -70,6 +71,6 @@ class AbstractEclipseIntegrationTest extends AbstractIdeIntegrationTest {
     }
 
     protected EclipseClasspathFixture getClasspath() {
-        return new EclipseClasspathFixture(distribution.testDir, distribution.userHomeDir)
+        return new EclipseClasspathFixture(testDirectory, new TestFile(executer.gradleUserHomeDir))
     }
 }
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathFixture.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathFixture.groovy
index e1c771c..8287099 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathFixture.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathFixture.groovy
@@ -15,9 +15,10 @@
  */
 package org.gradle.plugins.ide.eclipse
 
-import java.util.regex.Pattern
-import org.gradle.util.TestFile
 import org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager
+import org.gradle.test.fixtures.file.TestFile
+
+import java.util.regex.Pattern
 
 class EclipseClasspathFixture {
     final TestFile projectDir
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathIntegrationTest.groovy
index ece8c73..23cd77f 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathIntegrationTest.groovy
@@ -648,8 +648,6 @@ repositories {
 }
 
 dependencies {
-    scalaTools "org.scala-lang:scala-compiler:2.9.2"
-
     compile "org.scala-lang:scala-library:2.9.2"
     runtime "org.scala-lang:scala-swing:2.9.1"
     testCompile "org.scala-lang:scala-dbc:2.9.0"
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathRemoteResolutionIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathRemoteResolutionIntegrationTest.groovy
index 942247b..744bb40 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathRemoteResolutionIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathRemoteResolutionIntegrationTest.groovy
@@ -16,6 +16,7 @@
 package org.gradle.plugins.ide.eclipse
 
 import org.gradle.integtests.fixtures.TestResources
+import org.gradle.test.fixtures.maven.MavenHttpRepository
 import org.gradle.test.fixtures.server.http.HttpServer
 import org.junit.Before
 import org.junit.Rule
@@ -25,30 +26,30 @@ class EclipseClasspathRemoteResolutionIntegrationTest extends AbstractEclipseInt
 
     @Rule public final HttpServer server = new HttpServer()
     @Rule public final TestResources testResources = new TestResources()
+    final def repo = new MavenHttpRepository(server, "/repo", mavenRepo)
 
     @Before
     void "setup"() {
-        distribution.requireOwnUserHomeDir()
+        executer.requireOwnGradleUserHomeDir()
     }
 
     @Test
     void "does not break when source or javadoc artifacts are missing or broken"() {
 //        given:
-        def projectA = mavenRepo.module('group', 'projectA', '1.0').publish()
-        def projectB = mavenRepo.module('group', 'projectB', '1.0').publish()
+        def projectA = repo.module('group', 'projectA', '1.0').publish()
+        def projectB = repo.module('group', 'projectB', '1.0').publish()
         server.start()
 
 //        when:
         server.resetExpectations()
-        server.expectGet('/repo/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectGet('/repo/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-        server.expectGetMissing('/repo/group/projectA/1.0/projectA-1.0-sources.jar')
-        server.expectGetMissing('/repo/group/projectA/1.0/projectA-1.0-javadoc.jar')
-        server.expectGet('/repo/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
-        server.expectGet('/repo/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
-        server.addBroken('/repo/group/projectB/1.0/projectB-1.0-sources.jar')
-        server.addBroken('/repo/group/projectB/1.0/projectB-1.0-javadoc.jar')
-
+        projectA.pom.expectGet()
+        projectA.artifact.expectGet()
+        projectA.artifact(classifier: 'sources').expectGetMissing()
+        projectA.artifact(classifier: 'javadoc').expectGetMissing()
+        projectB.pom.expectGet()
+        projectB.artifact.expectGet()
+        projectB.artifact(classifier: 'sources').expectGetBroken()
+        projectB.artifact(classifier: 'javadoc').expectGetBroken()
 
 //        and:
         def result = runEclipseTask """
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathResolveIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathResolveIntegrationTest.groovy
new file mode 100644
index 0000000..a93a937
--- /dev/null
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathResolveIntegrationTest.groovy
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.plugins.ide.eclipse
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
+import org.junit.Rule
+
+class EclipseClasspathResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    @Rule ProgressLoggingFixture progressLogging
+
+    def "does not download source and javadoc artifacts from HTTP Maven repository until required"() {
+        given:
+        server.start()
+
+        def projectA = mavenRepo().module('group', 'projectA', '1.0')
+        projectA.artifact(classifier: 'sources')
+        projectA.artifact(classifier: 'javadoc')
+        projectA.publish()
+        def sourceJar = projectA.artifactFile(classifier: 'sources')
+        def javadocJar = projectA.artifactFile(classifier: 'javadoc')
+
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+}
+dependencies {
+    compile 'group:projectA:1.0'
+}
+eclipse { classpath { downloadJavadoc = true } }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
+}
+"""
+
+        when:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+
+        then:
+        succeeds 'listJars'
+
+        when:
+        server.resetExpectations()
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0-sources.jar', sourceJar)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0-javadoc.jar', javadocJar)
+
+        then:
+        succeeds 'eclipseClasspath'
+    }
+
+    def "only attempts to download missing artifacts from HTTP Maven repository once"() {
+        given:
+        server.start()
+
+        def projectA = mavenRepo().module('group', 'projectA', '1.0')
+        projectA.publish()
+
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+}
+dependencies {
+    compile 'group:projectA:1.0'
+}
+eclipse { classpath { downloadJavadoc = true } }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
+}
+"""
+
+        when:
+        server.resetExpectations()
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0-sources.jar')
+        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0-javadoc.jar')
+
+        then:
+        succeeds 'eclipseClasspath'
+
+        when:
+        server.resetExpectations()
+
+        then:
+        succeeds 'eclipseClasspath'
+    }
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest.groovy
index 74a27d8..4e56d1c 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest.groovy
@@ -326,7 +326,7 @@ eclipse {
     @Test
     @Issue("GRADLE-1157")
     void canHandleDependencyWithoutSourceJarInFlatDirRepo() {
-        def repoDir = testDir.createDir("repo")
+        def repoDir = testDirectory.createDir("repo")
         repoDir.createFile("lib-1.0.jar")
 
         runEclipseTask """
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpIntegrationTest.groovy
index b5803c3..22c52e6 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpIntegrationTest.groovy
@@ -18,12 +18,14 @@ package org.gradle.plugins.ide.eclipse
 import org.junit.Test
 import spock.lang.Issue
 
-// TODO: run prepareWebProject() only once per class for performance reasons (not as simply as it seems)
 class EclipseWtpIntegrationTest extends AbstractEclipseIntegrationTest {
+    void runSharedBuild() {
+        generateEclipseFilesForWebProject()
+    }
+
     @Test
     void projectDependenciesOfWebProjectAreMarkedAsJstUtilityProjects() {
-        prepareWebProject()
-
+        useSharedBuild = true;
         hasUtilityFacet("java1")
         hasUtilityFacet("java2")
         hasUtilityFacet("groovy")
@@ -31,8 +33,7 @@ class EclipseWtpIntegrationTest extends AbstractEclipseIntegrationTest {
 
     @Test
     void projectDependenciesOfWebProjectHaveNecessaryNaturesAdded() {
-        prepareWebProject()
-
+        useSharedBuild = true;
         hasNecessaryNaturesAdded("java1")
         hasNecessaryNaturesAdded("java2")
         hasNecessaryNaturesAdded("groovy")
@@ -40,8 +41,7 @@ class EclipseWtpIntegrationTest extends AbstractEclipseIntegrationTest {
 
     @Test
     void projectDependenciesOfWebProjectHaveNecessaryBuildersAdded() {
-        prepareWebProject()
-
+        useSharedBuild = true;
         hasNecessaryBuildersAdded("java1")
         hasNecessaryBuildersAdded("java2")
         hasNecessaryBuildersAdded("groovy")
@@ -49,8 +49,7 @@ class EclipseWtpIntegrationTest extends AbstractEclipseIntegrationTest {
 
     @Test
     void projectDependenciesOfWebProjectHaveTrimmedDownComponentSettingsFile() {
-        prepareWebProject()
-
+        useSharedBuild = true;
         hasTrimmedDownComponentSettingsFile("java1", "src/main/java", "src/main/resources")
         hasTrimmedDownComponentSettingsFile("java2", "src/main/java", "src/main/resources")
         hasTrimmedDownComponentSettingsFile("groovy", "src/main/java", "src/main/groovy", "src/main/resources")
@@ -58,8 +57,7 @@ class EclipseWtpIntegrationTest extends AbstractEclipseIntegrationTest {
 
     @Test
     void jarDependenciesOfUtilityProjectsAreFlaggedAsRuntimeDependency() {
-        prepareWebProject()
-
+        useSharedBuild = true;
         def classpath = parseClasspathFile(project: "java1")
 
         def firstLevelDep = classpath.classpathentry.find { it. at path.text().endsWith("myartifact-1.0.jar") }
@@ -72,8 +70,7 @@ class EclipseWtpIntegrationTest extends AbstractEclipseIntegrationTest {
 
     @Test
     void allProjectDependenciesOfWebProjectAreAddedAsRuntimeDependencies() {
-        prepareWebProject()
-
+        useSharedBuild = true;
         def projectModules = parseComponentFile(project: "web", print: true)
 
 		assert getDeployName(projectModules) == "web"
@@ -84,6 +81,8 @@ class EclipseWtpIntegrationTest extends AbstractEclipseIntegrationTest {
     @Test
     @Issue("GRADLE-1415")
     void canUseSelfResolvingFiles() {
+        useSharedBuild = false
+
         def buildFile = """
 apply plugin: "war"
 apply plugin: "eclipse"
@@ -104,9 +103,23 @@ dependencies {
         libEntriesInClasspathFileHaveFilenames("foo.jar")
     }
 
-    private prepareWebProject() {
+    @Test
+    @Issue("GRADLE-2526")
+    void overwritesDependentModules() {
+        useSharedBuild = false
+
+        generateEclipseFilesForWebProject()
+        def projectModules = parseComponentFile(project: "web")
+        assert getHandleFilenames(projectModules) == ["java1", "java2", "groovy", "myartifact-1.0.jar", "myartifactdep-1.0.jar"] as Set
+
+        generateEclipseFilesForWebProject("1.2.3")
+        def projectModules2 = parseComponentFile(project: "web")
+        assert getHandleFilenames(projectModules2) == ["java1", "java2", "groovy", "myartifact-1.2.3.jar", "myartifactdep-1.0.jar"] as Set
+    }
+
+    private generateEclipseFilesForWebProject(myArtifactVersion = "1.0") {
         def repoDir = file("repo")
-        maven(repoDir).module("mygroup", "myartifact").dependsOn("myartifactdep").publish()
+        maven(repoDir).module("mygroup", "myartifact", myArtifactVersion).dependsOn("myartifactdep").publish()
         maven(repoDir).module("mygroup", "myartifactdep").publish()
 
         def settingsFile = file("settings.gradle")
@@ -132,7 +145,7 @@ repositories {
 dependencies {
     compile project(":java1")
     compile project(":groovy")
-    runtime "mygroup:myartifact:1.0"
+    runtime "mygroup:myartifact:$myArtifactVersion"
 }
         """
 
@@ -149,7 +162,7 @@ repositories {
 
 dependencies {
     compile project(":java2")
-    runtime "mygroup:myartifact:1.0"
+    runtime "mygroup:myartifact:$myArtifactVersion"
 }
         """
 
@@ -165,7 +178,7 @@ repositories {
 }
 
 dependencies {
-    runtime "mygroup:myartifact:1.0"
+    runtime "mygroup:myartifact:$myArtifactVersion"
 }
         """
 
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaIntegrationTest.groovy
index 0cfb09f..5a61db1 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaIntegrationTest.groovy
@@ -16,17 +16,18 @@
 
 package org.gradle.plugins.ide.idea
 
-import java.util.regex.Pattern
 import org.custommonkey.xmlunit.Diff
 import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier
 import org.custommonkey.xmlunit.XMLAssert
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.internal.os.OperatingSystem
 import org.gradle.plugins.ide.AbstractIdeIntegrationTest
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
 
+import java.util.regex.Pattern
+
 class IdeaIntegrationTest extends AbstractIdeIntegrationTest {
     @Rule
     public final TestResources testResources = new TestResources()
@@ -80,7 +81,7 @@ apply plugin: 'idea'
 
     @Test
     void worksWithNonStandardLayout() {
-        executer.inDirectory(testDir.file('root')).withTasks('idea').run()
+        executer.inDirectory(testDirectory.file('root')).withTasks('idea').run()
 
         assertHasExpectedContents('root/root.ipr')
         assertHasExpectedContents('root/root.iml')
@@ -335,12 +336,12 @@ apply plugin: "idea"
     }
 
     private void assertHasExpectedContents(String path) {
-        TestFile file = testDir.file(path).assertIsFile()
-        TestFile expectedFile = testDir.file("expectedFiles/${path}.xml").assertIsFile()
+        TestFile file = testDirectory.file(path).assertIsFile()
+        TestFile expectedFile = testDirectory.file("expectedFiles/${path}.xml").assertIsFile()
 
         def expectedXml = expectedFile.text
 
-        def homeDir = distribution.userHomeDir.absolutePath.replace(File.separator, '/')
+        def homeDir = executer.gradleUserHomeDir.absolutePath.replace(File.separator, '/')
         def pattern = Pattern.compile(Pattern.quote(homeDir) + "/caches/artifacts-\\d+/filestore/([^/]+/[^/]+/[^/]+/[^/]+)/[a-z0-9]+/")
         def actualXml = file.text.replaceAll(pattern, '@CACHE_DIR@/$1/@SHA1@/')
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/FileContentMerger.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/FileContentMerger.groovy
index 3c1ec00..17e063a 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/FileContentMerger.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/FileContentMerger.groovy
@@ -16,6 +16,7 @@
 
 package org.gradle.plugins.ide.api
 
+import org.gradle.api.internal.ClosureBackedAction
 import org.gradle.listener.ActionBroadcast
 
 /**
@@ -42,7 +43,7 @@ class FileContentMerger {
      * @param closure The closure to execute.
      */
     public void beforeMerged(Closure closure) {
-        beforeMerged.add(closure)
+        beforeMerged.add(new ClosureBackedAction(closure))
     }
 
     /**
@@ -57,6 +58,6 @@ class FileContentMerger {
      * @param closure The closure to execute.
      */
     public void whenMerged(Closure closure) {
-        whenMerged.add(closure)
+        whenMerged.add(new ClosureBackedAction(closure))
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/PropertiesFileContentMerger.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/PropertiesFileContentMerger.groovy
index 46dcea1..affb762 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/PropertiesFileContentMerger.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/PropertiesFileContentMerger.groovy
@@ -15,6 +15,7 @@
  */
 package org.gradle.plugins.ide.api
 
+import org.gradle.api.internal.ClosureBackedAction
 import org.gradle.api.internal.PropertiesTransformer
 
 /**
@@ -38,6 +39,6 @@ class PropertiesFileContentMerger extends FileContentMerger {
     * @param closure The closure to execute when the Properties have been created.
     */
     void withProperties(Closure closure) {
-        transformer.addAction(closure)
+        transformer.addAction(new ClosureBackedAction<Properties>(closure, Closure.OWNER_FIRST))
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlFileContentMerger.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlFileContentMerger.groovy
index e59326e..fe58c62 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlFileContentMerger.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlFileContentMerger.groovy
@@ -16,7 +16,7 @@
 
 package org.gradle.plugins.ide.api
 
-import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.xml.XmlTransformer
 
 /**
  * Models the generation/parsing/merging capabilities. Adds xml-related hooks.
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlGeneratorTask.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlGeneratorTask.java
index b0b3952..848b284 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlGeneratorTask.java
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlGeneratorTask.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.plugins.ide.api;
 
-import org.gradle.api.internal.XmlTransformer;
+import org.gradle.api.internal.xml.XmlTransformer;
 import org.gradle.plugins.ide.internal.generator.generator.PersistableConfigurationObject;
 import org.gradle.plugins.ide.internal.generator.generator.PersistableConfigurationObjectGenerator;
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Classpath.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Classpath.groovy
index 8d0fa40..eac13e8 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Classpath.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Classpath.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
-import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
 import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Project.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Project.groovy
index bff57a5..5247ab8 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Project.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Project.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
-import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
 
 /**
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpComponent.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpComponent.groovy
index 2d835df..a4fd1e8 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpComponent.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpComponent.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
-import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
 
 /**
@@ -70,9 +70,9 @@ class WtpComponent extends XmlPersistableConfigurationObject {
         "defaultWtpComponent.xml"
     }
 
-    void configure(String deployName, String contextPath, List wbModuleEntries) {
-        this.wbModuleEntries.addAll(wbModuleEntries)
-        this.wbModuleEntries.unique()
+    void configure(String deployName, String contextPath, List newEntries) {
+        def entriesToBeKept = this.wbModuleEntries.findAll { !(it instanceof WbDependentModule) }
+        this.wbModuleEntries = (entriesToBeKept + newEntries).unique()
         if (deployName) {
             this.deployName = deployName
         }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacet.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacet.groovy
index 56e0e96..ca5e285 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacet.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacet.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
-import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
 
 /**
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModuleIml.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModuleIml.groovy
index 66770ab..b74914d 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModuleIml.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModuleIml.groovy
@@ -16,7 +16,7 @@
 
 package org.gradle.plugins.ide.idea.model
 
-import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.plugins.ide.api.XmlFileContentMerger
 
 /**
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Module.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Module.groovy
index f835879..1a20ed6 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Module.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Module.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.plugins.ide.idea.model
 
-import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
 import org.gradle.util.DeprecationLogger
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Project.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Project.groovy
index 7a1bc67..a0cdbd1 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Project.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Project.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.plugins.ide.idea.model
 
-import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
 
 /**
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Workspace.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Workspace.groovy
index e6438aa..a47f59c 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Workspace.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Workspace.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.plugins.ide.idea.model
 
-import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
 
 /**
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/PropertiesPersistableConfigurationObject.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/PropertiesPersistableConfigurationObject.groovy
index 282dc26..a43e57a 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/PropertiesPersistableConfigurationObject.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/PropertiesPersistableConfigurationObject.groovy
@@ -15,9 +15,9 @@
  */
 package org.gradle.plugins.ide.internal.generator
 
+import org.gradle.api.internal.ClosureBackedAction
 import org.gradle.api.internal.PropertiesTransformer
 
-
 abstract class PropertiesPersistableConfigurationObject extends AbstractPersistableConfigurationObject {
     private final PropertiesTransformer transformer
     private Properties properties
@@ -44,6 +44,6 @@ abstract class PropertiesPersistableConfigurationObject extends AbstractPersista
     protected abstract void load(Properties properties)
     
     void transformAction(Closure action) {
-        transformer.addAction(action)
+        transformer.addAction(new ClosureBackedAction<Properties>(action))
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/XmlPersistableConfigurationObject.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/XmlPersistableConfigurationObject.groovy
index 042e566..f609d5e 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/XmlPersistableConfigurationObject.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/XmlPersistableConfigurationObject.groovy
@@ -16,7 +16,7 @@
 package org.gradle.plugins.ide.internal.generator;
 
 
-import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.xml.XmlTransformer
 
 /**
  * A {@link org.gradle.plugins.ide.internal.generator.generator.PersistableConfigurationObject} which is stored in an XML file.
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ClasspathTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ClasspathTest.groovy
index 0ea3472..d560d8c 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ClasspathTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ClasspathTest.groovy
@@ -13,14 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.plugins.ide.eclipse.model;
+package org.gradle.plugins.ide.eclipse.model
 
-
-import org.gradle.api.internal.XmlTransformer
-import org.gradle.util.TemporaryFolder
+import org.gradle.api.internal.xml.XmlTransformer
+import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
-import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
 
 /**
  * @author Hans Dockter
@@ -41,7 +40,7 @@ public class ClasspathTest extends Specification {
     private final Classpath classpath = new Classpath(new XmlTransformer(), fileReferenceFactory)
 
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder()
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
     def setup() {
 
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ProjectTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ProjectTest.groovy
index c2ae20b..39f0a07 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ProjectTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ProjectTest.groovy
@@ -16,8 +16,8 @@
 package org.gradle.plugins.ide.eclipse.model;
 
 
-import org.gradle.api.internal.XmlTransformer
-import org.gradle.util.TemporaryFolder
+import org.gradle.api.internal.xml.XmlTransformer
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -31,7 +31,7 @@ public class ProjectTest extends Specification {
     def static final CUSTOM_LINKED_RESOURCES = [new Link('somename', 'sometype', 'somelocation', '')] as Set
 
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     final Project project = new Project(new XmlTransformer())
 
     def loadFromReader() {
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpComponentTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpComponentTest.groovy
index fd0302e..ea49d02 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpComponentTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpComponentTest.groovy
@@ -16,8 +16,8 @@
 package org.gradle.plugins.ide.eclipse.model
 
 import org.custommonkey.xmlunit.XMLUnit
-import org.gradle.api.internal.XmlTransformer
-import org.gradle.util.TemporaryFolder
+import org.gradle.api.internal.xml.XmlTransformer
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -32,7 +32,7 @@ public class WtpComponentTest extends Specification {
     private final WtpComponent component = new WtpComponent(new XmlTransformer())
 
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder()
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
     def "load existing XML file"() {
         when:
@@ -47,16 +47,20 @@ public class WtpComponentTest extends Specification {
     def "merge existing and new configuration"() {
         def constructorDeployName = 'build'
         def constructorContextPath = 'context'
-        def constructorWbModuleEntries = [createSomeWbModuleEntry()]
+        def constructorWbModuleEntries = createSomeWbModuleEntries()
 
         when:
         component.load(customComponentReader)
-        component.configure(constructorDeployName, constructorContextPath, constructorWbModuleEntries + [CUSTOM_WB_MODULE_ENTRIES[0]])
+        component.configure(constructorDeployName, constructorContextPath, constructorWbModuleEntries)
 
         then:
-        component.wbModuleEntries == CUSTOM_WB_MODULE_ENTRIES + constructorWbModuleEntries
         component.deployName == constructorDeployName
         component.contextPath == constructorContextPath
+        // dependent modules are replaced, other entries are added up
+        component.wbModuleEntries as Set == [
+                new WbDependentModule('/WEB-INF/lib', "module:/classpath/foo-1.2.3.jar"),
+                new WbResource("/WEB-INF/classes", "src/main/java"),
+                new WbResource("/WEB-INF/classes", "src/other/java")] as Set
     }
 
     def "load defaults"() {
@@ -85,7 +89,8 @@ public class WtpComponentTest extends Specification {
         getClass().getResourceAsStream('customOrgEclipseWstCommonComponent.xml')
     }
 
-    private WbProperty createSomeWbModuleEntry() {
-        return new WbProperty('someProp', 'someValue')
+    private List createSomeWbModuleEntries() {
+        [new WbDependentModule('/WEB-INF/lib', "module:/classpath/foo-1.2.3.jar"),
+        new WbResource("/WEB-INF/classes", "src/other/java")]
     }
 }
\ No newline at end of file
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacetTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacetTest.groovy
index fb64fd3..97affe7 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacetTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacetTest.groovy
@@ -16,11 +16,10 @@
 package org.gradle.plugins.ide.eclipse.model
 
 import org.custommonkey.xmlunit.XMLUnit
-import org.gradle.api.internal.XmlTransformer
-import org.gradle.plugins.ide.eclipse.model.Facet.FacetType;
-import org.gradle.util.TemporaryFolder
+import org.gradle.api.internal.xml.XmlTransformer
+import org.gradle.plugins.ide.eclipse.model.Facet.FacetType
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
-
 import spock.lang.Specification
 
 /**
@@ -32,7 +31,7 @@ public class WtpFacetTest extends Specification {
     private final WtpFacet facet = new WtpFacet(new XmlTransformer())
 
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder()
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
     def "load existing XML file"() {
         when:
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/FileReferenceFactoryTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/FileReferenceFactoryTest.groovy
index d764192..186104d 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/FileReferenceFactoryTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/FileReferenceFactoryTest.groovy
@@ -15,14 +15,14 @@
  */
 package org.gradle.plugins.ide.eclipse.model.internal
 
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.Matchers
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
 import org.junit.Rule
 import spock.lang.Specification
 
 class FileReferenceFactoryTest extends Specification {
-    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final TestFile rootDir = tmpDir.createDir("root")
     final FileReferenceFactory factory = new FileReferenceFactory()
 
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleTest.groovy
index 736721f..24454af 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleTest.groovy
@@ -16,7 +16,7 @@
 package org.gradle.plugins.ide.idea.model
 
 import org.gradle.api.JavaVersion
-import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.xml.XmlTransformer
 import spock.lang.Specification
 
 /**
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/PathFactoryTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/PathFactoryTest.groovy
index 96f30b3..17b2abe 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/PathFactoryTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/PathFactoryTest.groovy
@@ -15,17 +15,17 @@
  */
 package org.gradle.plugins.ide.idea.model
 
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 class PathFactoryTest extends Specification {
-    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final PathFactory factory = new PathFactory()
 
     def createsPathForAFileUnderARootDir() {
-        factory.addPathVariable('ROOT_DIR', tmpDir.dir)
+        factory.addPathVariable('ROOT_DIR', tmpDir.testDirectory)
 
         expect:
         def path = factory.path(tmpDir.file('a', 'b'))
@@ -34,8 +34,8 @@ class PathFactoryTest extends Specification {
     }
 
     def createsPathForAFileNotUnderARootDir() {
-        factory.addPathVariable('ROOT_DIR', tmpDir.dir)
-        def file = tmpDir.dir.parentFile.file('a')
+        factory.addPathVariable('ROOT_DIR', tmpDir.testDirectory)
+        def file = tmpDir.testDirectory.parentFile.file('a')
         def relpath = relpath(file)
 
         expect:
@@ -45,7 +45,7 @@ class PathFactoryTest extends Specification {
     }
 
     def usesTheClosestAncestorRootDirForAFileUnderMultipleRootDirs() {
-        factory.addPathVariable('ROOT_DIR', tmpDir.dir)
+        factory.addPathVariable('ROOT_DIR', tmpDir.testDirectory)
         factory.addPathVariable('SUB_DIR', tmpDir.file('sub'))
 
         expect:
@@ -56,10 +56,10 @@ class PathFactoryTest extends Specification {
 
     def createsPathForARootDir() {
         factory.addPathVariable('SUB_DIR', tmpDir.file('sub'))
-        factory.addPathVariable('ROOT_DIR', tmpDir.dir)
+        factory.addPathVariable('ROOT_DIR', tmpDir.testDirectory)
 
         expect:
-        def rootDir = factory.path(tmpDir.dir)
+        def rootDir = factory.path(tmpDir.testDirectory)
         rootDir.url == 'file://$ROOT_DIR$/'
         rootDir.relPath == '$ROOT_DIR$/'
 
@@ -69,7 +69,7 @@ class PathFactoryTest extends Specification {
     }
 
     def createsPathForAJarFile() {
-        factory.addPathVariable('ROOT_DIR', tmpDir.dir)
+        factory.addPathVariable('ROOT_DIR', tmpDir.testDirectory)
 
         expect:
         def path = factory.path(tmpDir.file('a.jar'))
@@ -78,7 +78,7 @@ class PathFactoryTest extends Specification {
     }
 
     def createsRelativePathForADescendantOfRootDir() {
-        factory.addPathVariable('ROOT_DIR', tmpDir.dir)
+        factory.addPathVariable('ROOT_DIR', tmpDir.testDirectory)
 
         expect:
         def path = factory.relativePath('ROOT_DIR', tmpDir.file('a/b'))
@@ -87,19 +87,19 @@ class PathFactoryTest extends Specification {
     }
 
     def createsRelativePathForAnAncestorOfRootDir() {
-        factory.addPathVariable('ROOT_DIR', tmpDir.dir)
+        factory.addPathVariable('ROOT_DIR', tmpDir.testDirectory)
 
         expect:
-        def path = factory.relativePath('ROOT_DIR', tmpDir.dir.parentFile.parentFile.file('a/b'))
+        def path = factory.relativePath('ROOT_DIR', tmpDir.testDirectory.parentFile.parentFile.file('a/b'))
         path.url == 'file://$ROOT_DIR$/../../a/b'
         path.relPath == '$ROOT_DIR$/../../a/b'
     }
 
     def createsRelativePathForASiblingOfRootDir() {
-        factory.addPathVariable('ROOT_DIR', tmpDir.dir)
+        factory.addPathVariable('ROOT_DIR', tmpDir.testDirectory)
 
         expect:
-        def path = factory.relativePath('ROOT_DIR', tmpDir.dir.parentFile.file('a'))
+        def path = factory.relativePath('ROOT_DIR', tmpDir.testDirectory.parentFile.file('a'))
         path.url == 'file://$ROOT_DIR$/../a'
         path.relPath == '$ROOT_DIR$/../a'
     }
@@ -149,7 +149,7 @@ class PathFactoryTest extends Specification {
     }
 
     def createsPathForAUrlWithPathVariables() {
-        factory.addPathVariable('ROOT_DIR', tmpDir.dir)
+        factory.addPathVariable('ROOT_DIR', tmpDir.testDirectory)
 
         expect:
         def path = factory.path('file://$ROOT_DIR$/c')
@@ -162,7 +162,7 @@ class PathFactoryTest extends Specification {
         TestFile childFile = tmpDir.file('sub/a/b')
 
         factory.addPathVariable('SUB_DIR', subDir)
-        factory.addPathVariable('ROOT_DIR', tmpDir.dir)
+        factory.addPathVariable('ROOT_DIR', tmpDir.testDirectory)
 
         expect:
 
@@ -191,7 +191,7 @@ class PathFactoryTest extends Specification {
         TestFile childFile = tmpDir.file('sub/a/b.jar')
 
         factory.addPathVariable('SUB_DIR', subDir)
-        factory.addPathVariable('ROOT_DIR', tmpDir.dir)
+        factory.addPathVariable('ROOT_DIR', tmpDir.testDirectory)
 
         expect:
 
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectTest.groovy
index 7268e26..14d3128 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectTest.groovy
@@ -16,7 +16,7 @@
 package org.gradle.plugins.ide.idea.model
 
 import org.gradle.api.JavaVersion
-import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.xml.XmlTransformer
 import spock.lang.Specification
 
 /**
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/GeneratorTaskTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/GeneratorTaskTest.groovy
index 9667e33..eb21192 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/GeneratorTaskTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/GeneratorTaskTest.groovy
@@ -17,13 +17,13 @@ package org.gradle.plugins.ide.internal
 
 import org.gradle.plugins.ide.api.GeneratorTask
 import org.gradle.plugins.ide.internal.generator.generator.Generator
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.HelperUtil
-import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
 
 class GeneratorTaskTest extends Specification {
-    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final Generator<TestConfigurationObject> generator = Mock()
     final File inputFile = tmpDir.file('input')
     final File outputFile = tmpDir.file('output')
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/generator/PropertiesPersistableConfigurationObjectTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/generator/PropertiesPersistableConfigurationObjectTest.groovy
index c3f99d9..b34ad6c 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/generator/PropertiesPersistableConfigurationObjectTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/generator/PropertiesPersistableConfigurationObjectTest.groovy
@@ -16,14 +16,13 @@
 package org.gradle.plugins.ide.internal.generator
 
 import org.gradle.api.internal.PropertiesTransformer
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.Matchers
-import org.gradle.util.TemporaryFolder
 import org.junit.Rule
-
 import spock.lang.Specification
 
 class PropertiesPersistableConfigurationObjectTest extends Specification {
-    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     String propertyValue
     final org.gradle.plugins.ide.internal.generator.PropertiesPersistableConfigurationObject object = new org.gradle.plugins.ide.internal.generator.PropertiesPersistableConfigurationObject(new PropertiesTransformer()) {
         @Override protected String getDefaultResourceName() {
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/generator/XmlPersistableConfigurationObjectTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/generator/XmlPersistableConfigurationObjectTest.groovy
index 610f2ab..2f48872 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/generator/XmlPersistableConfigurationObjectTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/generator/XmlPersistableConfigurationObjectTest.groovy
@@ -15,14 +15,14 @@
  */
 package org.gradle.plugins.ide.internal.generator
 
-import org.gradle.api.internal.XmlTransformer
-import org.gradle.util.TemporaryFolder
+import org.gradle.api.internal.xml.XmlTransformer
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.TextUtil
 import org.junit.Rule
 import spock.lang.Specification
 
 class XmlPersistableConfigurationObjectTest extends Specification {
-    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     String rootElement
     final XmlPersistableConfigurationObject object = new XmlPersistableConfigurationObject(new XmlTransformer()) {
         @Override protected String getDefaultResourceName() {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/api/tasks/TaskCommandLineConfigurationIntegrationSpec.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/api/tasks/TaskCommandLineConfigurationIntegrationSpec.groovy
index 3f269c8..f7f8d78 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/api/tasks/TaskCommandLineConfigurationIntegrationSpec.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/api/tasks/TaskCommandLineConfigurationIntegrationSpec.groovy
@@ -213,7 +213,7 @@ class TaskCommandLineConfigurationIntegrationSpec extends AbstractIntegrationSpe
         runAndFail 'tasks', '-all'
 
         then:
-        failure.assertHasDescription("Incorrect command line arguments: [-l, -l]. Task options require double dash, for example: 'gradle tasks --all'.")
+        failure.assertHasDescription("Problem configuring task :tasks from command line. Unknown command-line option '-l'.")
     }
 
     @Ignore
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/AntProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/AntProjectIntegrationTest.groovy
index d88b99d..d146909 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/AntProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/AntProjectIntegrationTest.groovy
@@ -15,11 +15,12 @@
  */
 package org.gradle.integtests
 
-import org.gradle.integtests.fixtures.ExecutionFailure
-import org.gradle.util.TestFile
-import org.junit.Test
-import static org.hamcrest.Matchers.*
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.executer.ExecutionFailure
+import org.gradle.test.fixtures.file.TestFile
+import org.junit.Test
+
+import static org.hamcrest.Matchers.startsWith
 
 public class AntProjectIntegrationTest extends AbstractIntegrationTest {
     @Test
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationTest.groovy
index 12978db..b0cbb6b 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationTest.groovy
@@ -18,8 +18,9 @@ package org.gradle.integtests
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.ScriptExecuter
 import org.gradle.internal.os.OperatingSystem
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.TextUtil
+
 import static org.hamcrest.Matchers.startsWith
 
 class ApplicationIntegrationTest extends AbstractIntegrationSpec{
@@ -52,7 +53,7 @@ class Main {
         run 'install'
 
         def builder = new ScriptExecuter()
-        builder.workingDir distribution.testDir.file('build/install/application/bin')
+        builder.workingDir file('build/install/application/bin')
         builder.executable "application"
         if (OperatingSystem.current().windows) {
             builder.environment('APPLICATION_OPTS', '-DtestValue=value -DtestValue2="some value" -DtestValue3="some value"')
@@ -83,19 +84,64 @@ class Main {
 '''
 
         when:
-        run 'install', 'distZip'
+        run 'install', 'distZip', 'distTar'
 
         then:
         def installDir = file('build/install/mega-app')
         installDir.assertIsDir()
-        checkApplicationImage(installDir)
+        checkApplicationImage('mega-app', installDir)
+
+        def distZipFile = file('build/distributions/mega-app.zip')
+        distZipFile.assertIsFile()
 
-        def distFile = distribution.testFile('build/distributions/mega-app.zip')
-        distFile.assertIsFile()
+        def distZipDir = file('build/unzip')
+        distZipFile.usingNativeTools().unzipTo(distZipDir)
+        checkApplicationImage('mega-app', distZipDir.file('mega-app'))
 
-        def distDir = distribution.testFile('build/unzip')
-        distFile.usingNativeTools().unzipTo(distDir)
-        checkApplicationImage(distDir.file('mega-app'))
+        def distTarFile = file('build/distributions/mega-app.tar')
+        distTarFile.assertIsFile()
+
+        def distTarDir = file('build/untar')
+        distTarFile.usingNativeTools().untarTo(distTarDir)
+        checkApplicationImage('mega-app', distTarDir.file('mega-app'))
+    }
+
+    def "check distribution contents when all defaults used"() {
+        file('settings.gradle') << 'rootProject.name = "application"'
+        file('build.gradle') << '''
+apply plugin: 'application'
+mainClassName = 'org.gradle.test.Main'
+'''
+        file('src/main/java/org/gradle/test/Main.java') << '''
+package org.gradle.test;
+
+class Main {
+    public static void main(String[] args) {
+    }
+}
+'''
+
+        when:
+        run 'install', 'distZip', 'distTar'
+
+        then:
+        def installDir = file('build/install/application')
+        installDir.assertIsDir()
+        checkApplicationImage('application', installDir)
+        
+        def distZipFile = file('build/distributions/application.zip')
+        distZipFile.assertIsFile()
+        
+        def distZipDir = file('build/unzip')
+        distZipFile.usingNativeTools().unzipTo(distZipDir)
+        checkApplicationImage('application', distZipDir.file('application'))
+        
+        def distTarFile = file('build/distributions/application.tar')
+        distTarFile.assertIsFile()
+		
+        def distTarDir = file('build/untar')
+        distTarFile.usingNativeTools().untarTo(distTarDir)
+        checkApplicationImage('application', distTarDir.file('application'))
     }
 
     def "installApp complains if install directory exists and doesn't look like previous install"() {
@@ -109,7 +155,7 @@ installApp.destinationDir = buildDir
         runAndFail 'installApp'
 
         then:
-        result.assertThatCause(startsWith("The specified installation directory '${distribution.testFile('build')}' is neither empty nor does it contain an installation"))
+        result.assertThatCause(startsWith("The specified installation directory '${file('build')}' is neither empty nor does it contain an installation"))
     }
 
     def "startScripts respect OS dependent line separators"() {
@@ -133,20 +179,20 @@ installApp.destinationDir = buildDir
         assertLineSeparators(generatedLinuxStartScript, TextUtil.unixLineSeparator, 164);
         assertLineSeparators(generatedLinuxStartScript, TextUtil.windowsLineSeparator, 1)
 
-        distribution.testFile("build/scripts/mega-app").exists()
+        file("build/scripts/mega-app").exists()
     }
 
-    private void checkApplicationImage(TestFile installDir) {
-        installDir.file('bin/mega-app').assertIsFile()
-        installDir.file('bin/mega-app.bat').assertIsFile()
-        installDir.file('lib/application.jar').assertIsFile()
-
+    private void checkApplicationImage(String applicationName, TestFile installDir) {
+        installDir.file("bin/${applicationName}").assertIsFile()
+        installDir.file("bin/${applicationName}.bat").assertIsFile()
+        installDir.file("lib/application.jar").assertIsFile()
+        
         def builder = new ScriptExecuter()
         builder.workingDir installDir.file('bin')
-        builder.executable 'mega-app'
+        builder.executable applicationName
         builder.standardOutput = new ByteArrayOutputStream()
         builder.errorOutput = new ByteArrayOutputStream()
-
+        
         def result = builder.run()
         result.assertNormalExitValue()
     }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BroadcastMessagingIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BroadcastMessagingIntegrationTest.groovy
deleted file mode 100644
index e255c23..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BroadcastMessagingIntegrationTest.groovy
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests
-
-import org.gradle.messaging.remote.internal.IncomingBroadcast
-import org.gradle.messaging.remote.internal.MessagingServices
-import org.gradle.messaging.remote.internal.OutgoingBroadcast
-import org.gradle.messaging.remote.internal.inet.SocketInetAddress
-import org.gradle.util.ConcurrentSpecification
-import java.util.concurrent.CountDownLatch
-import spock.lang.Ignore
-import java.util.concurrent.TimeUnit
-
- at Ignore
-class BroadcastMessagingIntegrationTest extends ConcurrentSpecification {
-    static final Random RANDOM = new Random()
-    final def testGroup = "test-group-${RANDOM.nextLong()}"
-    final def address = new SocketInetAddress(InetAddress.getByName("233.253.17.122"), 7914)
-
-    def "client can discover and send messages to server"() {
-        TestService incoming = Mock()
-        def server = newServer()
-        def client = newClient()
-        def discovered = startsAsyncAction()
-
-        given:
-        server.addIncoming(incoming)
-
-        when:
-        discovered.started {
-            client.outgoing.doStuff("message")
-        }
-
-        then:
-        1 * incoming.doStuff("message") >> { discovered.done() }
-
-        cleanup:
-        server?.stop()
-        client?.stop()
-    }
-
-    def "multiple clients can discover server"() {
-        TestService incoming = Mock()
-        def server = newServer()
-        def client1 = newClient()
-        def client2 = newClient()
-        def discovered1 = startsAsyncAction()
-        def discovered2 = startsAsyncAction()
-
-        given:
-        server.addIncoming(incoming)
-
-        when:
-        discovered1.started {
-            client1.outgoing.doStuff("client1")
-        }
-        discovered2.started {
-            client2.outgoing.doStuff("client2")
-        }
-
-        then:
-        1 * incoming.doStuff("client1") >> { discovered1.done() }
-        1 * incoming.doStuff("client2") >> { discovered2.done() }
-
-        cleanup:
-        server?.stop()
-        client1?.stop()
-        client2?.stop()
-    }
-
-    @Ignore
-    def "client can broadcast to multiple servers"() {
-        TestService incoming1 = Mock()
-        TestService incoming2 = Mock()
-        def server1 = newServer()
-        def server2 = newServer()
-        def client = newClient()
-        def broadcast = new CountDownLatch(2)
-
-        given:
-        server1.addIncoming(incoming1)
-        server2.addIncoming(incoming2)
-
-        when:
-        start {
-            client.outgoing.doStuff("message")
-            broadcast.await()
-        }
-        finished()
-
-        then:
-        1 * incoming1.doStuff("message") >> { broadcast.countDown() }
-        1 * incoming2.doStuff("message") >> { broadcast.countDown() }
-
-        cleanup:
-        server1?.stop()
-        server2?.stop()
-        client?.stop()
-    }
-
-    def "client stop flushes messages"() {
-        TestService incoming = Mock()
-        def client
-        def server = newServer()
-
-        given:
-        server.addIncoming(incoming)
-
-        when:
-        start {
-            client = newClient()
-            client.outgoing.doStuff("message1")
-            client.outgoing.doStuff("message2")
-            client.outgoing.doStuff("message3")
-            client.stop()
-        }
-        finished()
-
-        then:
-        1 * incoming.doStuff("message1")
-        1 * incoming.doStuff("message2")
-        1 * incoming.doStuff("message3")
-
-        cleanup:
-        client?.stop()
-        server?.stop()
-    }
-
-    def "client can start broadcasting before server started"() {
-        TestService incoming = Mock()
-        def client = newClient()
-        def server
-        def discovered = startsAsyncAction()
-
-        when:
-        discovered.started {
-            client.outgoing.doStuff("message")
-            server = newServer()
-            server.addIncoming(incoming)
-        }
-
-        then:
-        1 * incoming.doStuff("message") >> { discovered.done() }
-
-        cleanup:
-        server?.stop()
-        client?.stop()
-    }
-
-    def "client can start broadcasting after server started"() {
-        TestService incoming = Mock()
-        def client
-        def server = newServer()
-        def discovered = startsAsyncAction()
-
-        given:
-        server.addIncoming(incoming)
-
-        when:
-        discovered.started {
-            client = newClient()
-            client.outgoing.doStuff("message")
-        }
-
-        then:
-        1 * incoming.doStuff("message") >> { discovered.done() }
-
-        cleanup:
-        server?.stop()
-        client?.stop()
-    }
-
-    def "client can discover restarted server"() {
-        TestService incoming1 = Mock()
-        TestService incoming2 = Mock()
-        def server1 = newServer()
-        def server2
-        def client = newClient()
-        def discovered1 = startsAsyncAction()
-        def discovered2 = startsAsyncAction()
-
-        given:
-        server1.addIncoming(incoming1)
-
-        when:
-        discovered1.started {
-            client.outgoing.doStuff("message1")
-        }
-
-        then:
-        1 * incoming1.doStuff("message1") >> { discovered1.done() }
-
-        when:
-        discovered2.started {
-            server1.stop()
-            server2 = newServer()
-            server2.addIncoming(incoming2)
-            client.outgoing.doStuff("message2")
-        }
-
-        then:
-        1 * incoming2.doStuff("message2") >> { discovered2.done() }
-
-        cleanup:
-        client?.stop()
-        server1?.stop()
-        server2?.stop()
-    }
-
-    def "client can stop when no server has been discovered"() {
-        def client = newClient()
-
-        when:
-        start {
-            client.outgoing.doStuff("message1")
-            client.stop()
-        }.completesWithin(6, TimeUnit.SECONDS)
-
-        then:
-        notThrown(RuntimeException)
-
-        cleanup:
-        client?.stop()
-    }
-
-    def "can stop client when it has not sent any messages"() {
-        def client = newClient()
-
-        when:
-        start {
-            client.stop()
-        }
-        finished()
-
-        then:
-        notThrown(RuntimeException)
-
-        cleanup:
-        client?.stop()
-    }
-
-    def "groups are independent"() {
-        def server1 = newServer("${testGroup}-1")
-        def server2 = newServer("${testGroup}-2")
-        def client1 = newClient("${testGroup}-1")
-        def client2 = newClient("${testGroup}-2")
-        def received = new CountDownLatch(2)
-        TestService incoming1 = Mock()
-        TestService incoming2 = Mock()
-
-        given:
-        server1.addIncoming(incoming1)
-        server2.addIncoming(incoming2)
-
-        when:
-        start {
-            client1.outgoing.doStuff("client1")
-            client2.outgoing.doStuff("client2")
-            client1.stop()
-            client2.stop()
-            server1.stop()
-            server2.stop()
-        }
-        finished()
-
-        then:
-        1 * incoming1.doStuff("client1") >> { received.countDown() }
-        1 * incoming2.doStuff("client2") >> { received.countDown() }
-        0 * incoming1._
-        0 * incoming2._
-
-        cleanup:
-        server1?.stop()
-        server2?.stop()
-        client1?.stop()
-        client2?.stop()
-    }
-
-    private Client newClient(String group = testGroup) {
-        return new Client(group)
-    }
-
-    private Server newServer(String group = testGroup) {
-        return new Server(group)
-    }
-
-
-    class Server {
-        final def services
-        final def broadcast
-
-        Server(String group) {
-            services = new MessagingServices(getClass().classLoader, group, address)
-            broadcast = services.get(IncomingBroadcast.class)
-        }
-
-        void addIncoming(TestService value) {
-            broadcast.addIncoming(TestService.class, value)
-        }
-
-        void stop() {
-            services.stop()
-        }
-    }
-
-    class Client {
-        final def services
-        final def lookup
-        final TestService outgoing
-
-        Client(String group) {
-            services = new MessagingServices(getClass().classLoader, group, address)
-            lookup = services.get(OutgoingBroadcast.class)
-            outgoing = lookup.addOutgoing(TestService)
-        }
-
-        void stop() {
-            services.stop()
-        }
-    }
-}
-
-interface TestService {
-    void doStuff(String param)
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildAggregationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildAggregationIntegrationTest.groovy
index 07dfe41..8a8eb23 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildAggregationIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildAggregationIntegrationTest.groovy
@@ -15,21 +15,16 @@
  */
 package org.gradle.integtests
 
-import org.gradle.integtests.fixtures.ExecutionFailure
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.executer.ExecutionFailure
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Test
-import static org.hamcrest.Matchers.*
 
-class BuildAggregationIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class BuildAggregationIntegrationTest extends AbstractIntegrationTest {
 
     @Test
     public void canExecuteAnotherBuildFromBuild() {
-        dist.testFile('build.gradle') << '''
+        file('build.gradle') << '''
             assert gradle.parent == null
             task build(type: GradleBuild) {
                 dir = 'other'
@@ -38,7 +33,7 @@ class BuildAggregationIntegrationTest {
             }
 '''
 
-        dist.testFile('other/build.gradle') << '''
+        file('other/build.gradle') << '''
             assert gradle.parent != null
             task dostuff << {
                 assert gradle.parent != null
@@ -50,12 +45,12 @@ class BuildAggregationIntegrationTest {
 
     @Test
     public void treatsBuildSrcProjectAsANestedBuild() {
-        dist.testFile('build.gradle') << '''
+        file('build.gradle') << '''
             assert gradle.parent == null
             task build
 '''
 
-        dist.testFile('buildSrc/build.gradle') << '''
+        file('buildSrc/build.gradle') << '''
             apply plugin: 'java'
             assert gradle.parent != null
             classes << {
@@ -68,11 +63,11 @@ class BuildAggregationIntegrationTest {
 
     @Test
     public void reportsNestedBuildFailure() {
-        TestFile other = dist.testFile('other.gradle') << '''
+        TestFile other = file('other.gradle') << '''
             throw new ArithmeticException('broken')
 '''
 
-        dist.testFile('build.gradle') << '''
+        file('build.gradle') << '''
             task build(type: GradleBuild) {
                 buildFile = 'other.gradle'
                 startParameter.searchUpwards = false
@@ -88,7 +83,7 @@ class BuildAggregationIntegrationTest {
 
     @Test
     public void reportsBuildSrcFailure() {
-        dist.testFile('buildSrc/src/main/java/Broken.java') << 'broken!'
+        file('buildSrc/src/main/java/Broken.java') << 'broken!'
         ExecutionFailure failure = executer.runWithFailure()
         failure.assertHasDescription('Execution failed for task \':compileJava\'')
     }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptClasspathIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptClasspathIntegrationTest.java
index f2ec423..950ce2f 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptClasspathIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptClasspathIntegrationTest.java
@@ -16,8 +16,8 @@
 package org.gradle.integtests;
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest;
-import org.gradle.integtests.fixtures.ArtifactBuilder;
-import org.gradle.integtests.fixtures.ExecutionFailure;
+import org.gradle.integtests.fixtures.executer.ArtifactBuilder;
+import org.gradle.integtests.fixtures.executer.ExecutionFailure;
 import org.junit.Ignore;
 import org.junit.Test;
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptErrorIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptErrorIntegrationTest.java
index a93718a..7a68f75 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptErrorIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptErrorIntegrationTest.java
@@ -16,8 +16,8 @@
 package org.gradle.integtests;
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest;
-import org.gradle.integtests.fixtures.ExecutionFailure;
-import org.gradle.util.TestFile;
+import org.gradle.integtests.fixtures.executer.ExecutionFailure;
+import org.gradle.test.fixtures.file.TestFile;
 import org.junit.Ignore;
 import org.junit.Test;
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptExecutionIntegrationTest.groovy
index 23d8080..cb5cd9a 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptExecutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptExecutionIntegrationTest.groovy
@@ -17,12 +17,14 @@
 
 package org.gradle.integtests
 
-import org.gradle.integtests.fixtures.ExecutionResult
-import org.gradle.util.TestFile
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.executer.ExecutionResult
+import org.gradle.test.fixtures.file.TestFile
+import org.junit.Test
+
+import static org.hamcrest.Matchers.containsString
+import static org.hamcrest.Matchers.not
+import static org.junit.Assert.assertThat
 
 class BuildScriptExecutionIntegrationTest extends AbstractIntegrationTest {
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildSourceBuilderIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildSourceBuilderIntegrationTest.groovy
index 3e912e0..771497c 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildSourceBuilderIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildSourceBuilderIntegrationTest.groovy
@@ -17,8 +17,8 @@
 package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.file.TestFile
 import spock.lang.Issue
-import org.gradle.util.TestFile
 
 class BuildSourceBuilderIntegrationTest extends AbstractIntegrationSpec {
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy
index e94d0fa..127188a 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy
@@ -20,10 +20,10 @@ import org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager
 import org.gradle.groovy.scripts.ScriptSource
 import org.gradle.groovy.scripts.UriScriptSource
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.maven.MavenRepository
 import org.gradle.test.fixtures.server.http.HttpServer
 import org.gradle.util.GradleVersion
-import org.gradle.util.TestFile
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -50,12 +50,12 @@ public class CacheProjectIntegrationTest extends AbstractIntegrationTest {
     @Before
     public void setUp() {
         // Use own home dir so we don't blast the shared one when we run with -C rebuild
-        distribution.requireOwnUserHomeDir()
+        executer.requireOwnGradleUserHomeDir()
 
         String version = GradleVersion.current().version
-        projectDir = distribution.getTestDir().file("project")
+        projectDir = file("project")
         projectDir.mkdirs()
-        userHomeDir = distribution.getUserHomeDir()
+        userHomeDir = new TestFile(executer.gradleUserHomeDir)
         buildFile = projectDir.file('build.gradle')
         ScriptSource source = new UriScriptSource("build file", buildFile)
         propertiesFile = userHomeDir.file("caches/$version/scripts/$source.className/ProjectScript/no_buildscript/cache.properties")
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CharacterEncodingIntegTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CharacterEncodingIntegTest.groovy
index 8a702f3..fdd8b36 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CharacterEncodingIntegTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CharacterEncodingIntegTest.groovy
@@ -16,13 +16,14 @@
 
 package org.gradle.integtests
 
-import java.nio.charset.Charset
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import spock.lang.IgnoreIf
 import spock.lang.Unroll
 
- at IgnoreIf({ !GradleDistributionExecuter.systemPropertyExecuter.forks })
+import java.nio.charset.Charset
+
+ at IgnoreIf({ GradleContextualExecuter.embedded })
 class CharacterEncodingIntegTest extends AbstractIntegrationSpec {
 
     def executerEncoding(String inputEncoding) {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy
index edebb8c..097ab24 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy
@@ -15,23 +15,30 @@
  */
 package org.gradle.integtests
 
-import org.gradle.integtests.fixtures.ExecutionFailure
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.executer.ExecutionFailure
+import org.gradle.internal.jvm.Jvm
 import org.gradle.internal.nativeplatform.filesystem.FileSystems
 import org.gradle.internal.os.OperatingSystem
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.PreconditionVerifier
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
-import org.gradle.util.*
-import org.gradle.internal.jvm.Jvm
 
-public class CommandLineIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter(GradleDistributionExecuter.Executer.forking)
+public class CommandLineIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final TestResources resources = new TestResources()
     @Rule public final PreconditionVerifier verifier = new PreconditionVerifier()
 
+    @Before
+    void setup() {
+        executer.requireGradleHome(true)
+    }
+
     @Test
     public void hasNonZeroExitCodeOnBuildFailure() {
         ExecutionFailure failure = executer.withTasks('unknown').runWithFailure()
@@ -71,7 +78,7 @@ public class CommandLineIntegrationTest {
 
     @Test
     public void failsWhenJavaHomeDoesNotPointToAJavaInstallation() {
-        def failure = executer.withEnvironmentVars('JAVA_HOME': dist.testDir).withTasks('checkJavaHome').runWithFailure()
+        def failure = executer.withEnvironmentVars('JAVA_HOME': testDirectory).withTasks('checkJavaHome').runWithFailure()
         assert failure.output.contains('ERROR: JAVA_HOME is set to an invalid directory')
     }
 
@@ -83,7 +90,7 @@ public class CommandLineIntegrationTest {
             path = ''
         } else {
             // Set up a fake bin directory, containing the things that the script needs, minus any java that might be in /usr/bin
-            def binDir = dist.testFile('fake-bin')
+            def binDir = file('fake-bin')
             ['basename', 'dirname', 'uname', 'which', 'bash'].each { linkToBinary(it, binDir) }
             path = binDir.absolutePath
         }
@@ -105,7 +112,7 @@ public class CommandLineIntegrationTest {
     @Test
     public void canDefineGradleUserHomeViaEnvironmentVariable() {
         // the actual testing is done in the build script.
-        File gradleUserHomeDir = dist.testDir.file('customUserHome')
+        File gradleUserHomeDir = file('customUserHome')
         executer.withGradleUserHomeDir(null).withEnvironmentVars('GRADLE_USER_HOME': gradleUserHomeDir.absolutePath).withTasks("checkGradleUserHomeViaSystemEnv").run();
     }
 
@@ -124,7 +131,7 @@ public class CommandLineIntegrationTest {
     @Test
     public void canSpecifySystemPropertiesUsingGradleOptsEnvironmentVariable() {
         // the actual testing is done in the build script.
-        executer.withTasks("checkSystemProperty").withEnvironmentVars("GRADLE_OPTS": '-DcustomProp1=custom-value "-DcustomProp2=custom value"').run();
+        executer.withTasks("checkSystemProperty").withGradleOpts('-DcustomProp1=custom-value', "-DcustomProp2=custom value").run();
     }
 
     @Test
@@ -136,20 +143,20 @@ public class CommandLineIntegrationTest {
     @Test
     public void allowsReconfiguringProjectCacheDirWithRelativeDir() {
         //given
-        dist.testFile("build.gradle").write "task foo { outputs.file file('out'); doLast { } }"
+        file("build.gradle").write "task foo { outputs.file file('out'); doLast { } }"
 
         //when
         executer.withTasks("foo").withArguments("--project-cache-dir", ".foo").run()
 
         //then
-        assert dist.testFile(".foo").exists()
+        assert file(".foo").exists()
     }
 
     @Test
     public void allowsReconfiguringProjectCacheDirWithAbsoluteDir() {
         //given
-        dist.testFile("build.gradle").write "task foo { outputs.file file('out'); doLast { } }"
-        File someAbsoluteDir = dist.testFile("foo/bar/baz").absoluteFile
+        file("build.gradle").write "task foo { outputs.file file('out'); doLast { } }"
+        File someAbsoluteDir = file("foo/bar/baz").absoluteFile
         assert someAbsoluteDir.absolute
 
         //when
@@ -162,28 +169,28 @@ public class CommandLineIntegrationTest {
     @Test
     public void systemPropGradleUserHomeHasPrecedenceOverEnvVariable() {
         // the actual testing is done in the build script.
-        File gradleUserHomeDir = dist.testFile("customUserHome")
-        File systemPropGradleUserHomeDir = dist.testFile("systemPropCustomUserHome")
+        File gradleUserHomeDir = file("customUserHome")
+        File systemPropGradleUserHomeDir = file("systemPropCustomUserHome")
         executer.withGradleUserHomeDir(null).withArguments("-Dgradle.user.home=" + systemPropGradleUserHomeDir.absolutePath).withEnvironmentVars('GRADLE_USER_HOME': gradleUserHomeDir.absolutePath).withTasks("checkSystemPropertyGradleUserHomeHasPrecedence").run()
     }
 
     @Test
     @Requires(TestPrecondition.SYMLINKS)
     public void resolvesLinksWhenDeterminingHomeDirectory() {
-        def script = dist.testFile('bin/my app')
+        def script = file('bin/my app')
         script.parentFile.createDir()
-        FileSystems.default.createSymbolicLink(script, dist.gradleHomeDir.file('bin/gradle'))
+        FileSystems.default.createSymbolicLink(script, distribution.gradleHomeDir.file('bin/gradle'))
 
         def result = executer.usingExecutable(script.absolutePath).withTasks("help").run()
         assert result.output.contains("my app")
 
         // Don't follow links when cleaning up test files
-        dist.temporaryFolder.dir.usingNativeTools().deleteDir()
+        testDirectory.usingNativeTools().deleteDir()
     }
 
     @Test
     public void usesScriptBaseNameAsApplicationNameForUseInLogMessages() {
-        def binDir = dist.gradleHomeDir.file('bin')
+        def binDir = distribution.gradleHomeDir.file('bin')
         def newScript = binDir.file(OperatingSystem.current().getScriptName('my app'))
         binDir.file(OperatingSystem.current().getScriptName('gradle')).copyTo(newScript)
         newScript.permissions = 'rwx------'
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CustomPluginIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CustomPluginIntegrationTest.groovy
index 2f95431..aa729ba 100755
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CustomPluginIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CustomPluginIntegrationTest.groovy
@@ -16,7 +16,7 @@
 package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.ArtifactBuilder
+import org.gradle.integtests.fixtures.executer.ArtifactBuilder
 
 public class CustomPluginIntegrationTest extends AbstractIntegrationSpec {
     public void "can reference plugin in buildSrc by id"() {
@@ -156,7 +156,7 @@ repositories { mavenCentral() }
 dependencies {
     compile gradleApi()
     groovy localGroovy()
-    testCompile 'junit:junit:4.8.2'
+    testCompile 'junit:junit:4.11'
 }
 """
 
@@ -197,7 +197,7 @@ repositories { mavenCentral() }
 dependencies {
     compile gradleApi()
     groovy localGroovy()
-    testCompile 'junit:junit:4.8.2'
+    testCompile 'junit:junit:4.11'
 }
 """
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptErrorIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptErrorIntegrationTest.groovy
index 38bce18..22f3df7 100755
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptErrorIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptErrorIntegrationTest.groovy
@@ -15,11 +15,12 @@
  */
 package org.gradle.integtests
 
-import org.gradle.integtests.fixtures.ExecutionFailure
-import org.gradle.util.TestFile
-import org.junit.Test
-import static org.hamcrest.Matchers.*
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.executer.ExecutionFailure
+import org.gradle.test.fixtures.file.TestFile
+import org.junit.Test
+
+import static org.hamcrest.Matchers.containsString
 
 class ExternalScriptErrorIntegrationTest extends AbstractIntegrationTest {
     @Test
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptExecutionIntegrationTest.groovy
index 22c9f65..11831c7 100755
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptExecutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptExecutionIntegrationTest.groovy
@@ -18,12 +18,12 @@
 package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.ArtifactBuilder
-import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.integtests.fixtures.executer.ArtifactBuilder
+import org.gradle.integtests.fixtures.executer.ExecutionResult
+import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.server.http.HttpServer
 import org.gradle.test.matchers.UserAgentMatcher
 import org.gradle.util.GradleVersion
-import org.gradle.util.TestFile
 import org.junit.Rule
 import org.junit.Test
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalBuildIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalBuildIntegrationTest.groovy
index 29cac1d..9a59e3f 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalBuildIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalBuildIntegrationTest.groovy
@@ -18,11 +18,12 @@
 
 package org.gradle.integtests
 
-import org.gradle.integtests.fixtures.TestResources
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.util.TestFile
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
+
 import static org.hamcrest.Matchers.equalTo
 import static org.junit.Assert.assertThat
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalGroovyProjectBuildIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalGroovyProjectBuildIntegrationTest.groovy
index b1adb7c..f02eff3 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalGroovyProjectBuildIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalGroovyProjectBuildIntegrationTest.groovy
@@ -15,20 +15,16 @@
  */
 package org.gradle.integtests
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Test
 
-class IncrementalGroovyProjectBuildIntegrationTest {
-    @Rule public final GradleDistribution distribution = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class IncrementalGroovyProjectBuildIntegrationTest extends AbstractIntegrationTest {
 
     @Test
     public void doesNotRebuildGroovydocIfSourceHasNotChanged() {
-        distribution.testFile("src/main/groovy/BuildClass.java") << 'public class BuildClass { }'
-        distribution.testFile("build.gradle") << '''
+        file("src/main/groovy/BuildClass.java") << 'public class BuildClass { }'
+        file("build.gradle") << '''
             apply plugin: 'groovy'
             dependencies { groovy localGroovy() }
             groovydoc {
@@ -38,7 +34,7 @@ class IncrementalGroovyProjectBuildIntegrationTest {
 
         executer.withTasks("groovydoc").run();
 
-        TestFile indexFile = distribution.testFile("build/docs/groovydoc/index.html");
+        TestFile indexFile = file("build/docs/groovydoc/index.html");
         indexFile.assertIsFile();
         TestFile.Snapshot snapshot = indexFile.snapshot();
 
@@ -46,7 +42,7 @@ class IncrementalGroovyProjectBuildIntegrationTest {
 
         indexFile.assertHasNotChangedSince(snapshot);
 
-        distribution.testFile("build.gradle").append("groovydoc.link('http://download.oracle.com/javase/1.5.0/docs/api', 'java.')")
+        file("build.gradle").append("groovydoc.link('http://download.oracle.com/javase/1.5.0/docs/api', 'java.')")
 
         executer.withTasks("groovydoc").run().assertTaskNotSkipped(':groovydoc');
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalJavaProjectBuildIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalJavaProjectBuildIntegrationTest.groovy
index 73e0d4c..dc24d42 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalJavaProjectBuildIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalJavaProjectBuildIntegrationTest.groovy
@@ -15,43 +15,39 @@
  */
 package org.gradle.integtests
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.junit.Rule
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Test
-import org.gradle.util.TestFile
 
-class IncrementalJavaProjectBuildIntegrationTest {
-    @Rule public final GradleDistribution distribution = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class IncrementalJavaProjectBuildIntegrationTest extends AbstractIntegrationTest {
 
     @Test
     public void removesStateResources() {
-        distribution.testFile('build.gradle') << 'apply plugin: \'java\''
-        distribution.testFile('src/main/resources/org/gradle/resource.txt').createFile()
+        file('build.gradle') << 'apply plugin: \'java\''
+        file('src/main/resources/org/gradle/resource.txt').createFile()
 
         executer.withTasks('classes').run()
-        distribution.testFile('build/resources/main').assertHasDescendants('org/gradle/resource.txt')
+        file('build/resources/main').assertHasDescendants('org/gradle/resource.txt')
 
-        distribution.testFile('src/main/resources/org/gradle/resource.txt').assertIsFile().delete()
-        distribution.testFile('src/main/resources/org/gradle/resource2.txt').createFile()
+        file('src/main/resources/org/gradle/resource.txt').assertIsFile().delete()
+        file('src/main/resources/org/gradle/resource2.txt').createFile()
 
         executer.withTasks('classes').run()
-        distribution.testFile('build/resources/main').assertHasDescendants('org/gradle/resource2.txt')
+        file('build/resources/main').assertHasDescendants('org/gradle/resource2.txt')
     }
 
     @Test
     public void doesNotRebuildJarIfSourceHasNotChanged() {
         // Use own home dir so we don't blast the shared one when we run with -C rebuild
-        distribution.requireOwnUserHomeDir()
+        executer.requireOwnGradleUserHomeDir()
 
-        distribution.testFile("src/main/java/BuildClass.java") << 'public class BuildClass { }'
-        distribution.testFile("build.gradle") << "apply plugin: 'java'"
-        distribution.testFile("settings.gradle") << "rootProject.name = 'project'"
+        file("src/main/java/BuildClass.java") << 'public class BuildClass { }'
+        file("build.gradle") << "apply plugin: 'java'"
+        file("settings.gradle") << "rootProject.name = 'project'"
 
         executer.withTasks("jar").run();
 
-        TestFile jar = distribution.testFile("build/libs/project.jar");
+        TestFile jar = file("build/libs/project.jar");
         jar.assertIsFile();
         TestFile.Snapshot snapshot = jar.snapshot();
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy
index 6bb21e8..db95eca 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy
@@ -15,17 +15,13 @@
  */
 package org.gradle.integtests
 
-import org.junit.Assert
-import org.junit.Ignore
-import org.junit.Rule
-import org.junit.Test
-import org.gradle.integtests.fixtures.*
-import static org.hamcrest.Matchers.startsWith
-import org.junit.Before
-
-class IncrementalTestIntegrationTest {
-    @Rule public final GradleDistribution distribution = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.*
+
+class IncrementalTestIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final TestResources resources = new TestResources()
 
     @Before
@@ -35,10 +31,9 @@ class IncrementalTestIntegrationTest {
 
     @Test
     public void doesNotRunStaleTests() {
-        def failure = executer.withTasks('test').runWithFailure()
-        failure.assertThatCause(startsWith('There were failing tests.'))
+        executer.withTasks('test').runWithFailure().assertTestsFailed()
 
-        distribution.testFile('src/test/java/Broken.java').assertIsFile().delete()
+        file('src/test/java/Broken.java').assertIsFile().delete()
 
         executer.withTasks('test').run()
     }
@@ -48,13 +43,13 @@ class IncrementalTestIntegrationTest {
         executer.withTasks('test').run()
 
         // Change a production class
-        distribution.testFile('src/main/java/MainClass.java').assertIsFile().copyFrom(distribution.testFile('NewMainClass.java'))
+        file('src/main/java/MainClass.java').assertIsFile().copyFrom(file('NewMainClass.java'))
 
         executer.withTasks('test').run().assertTasksNotSkipped(':compileJava', ':classes', ':compileTestJava', ':testClasses', ':test')
         executer.withTasks('test').run().assertTasksNotSkipped()
         
         // Change a test class
-        distribution.testFile('src/test/java/Ok.java').assertIsFile().copyFrom(distribution.testFile('NewOk.java'))
+        file('src/test/java/Ok.java').assertIsFile().copyFrom(file('NewOk.java'))
 
         executer.withTasks('test').run().assertTasksNotSkipped(':compileTestJava', ':testClasses', ':test')
         executer.withTasks('test').run().assertTasksNotSkipped()
@@ -64,11 +59,11 @@ class IncrementalTestIntegrationTest {
     public void executesTestsWhenSelectedTestsChange() {
         executer.withTasks('test').run()
 
-        def result = new JUnitTestExecutionResult(distribution.testDir)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('JUnitTest')
 
         // Include more tests
-        distribution.testFile('build.gradle').append 'test.include "**/*Extra*"\n'
+        file('build.gradle').append 'test.include "**/*Extra*"\n'
 
         executer.withTasks('test').run().assertTasksNotSkipped(':test')
         result.assertTestClassesExecuted('JUnitTest', 'JUnitExtra')
@@ -81,11 +76,12 @@ class IncrementalTestIntegrationTest {
         executer.withTasks('test').run().assertTasksNotSkipped()
 
         // Switch test framework
-        distribution.testFile('build.gradle').append 'test.useTestNG()\n'
+        file('build.gradle').append 'test.useTestNG()\n'
 
-        executer.withTasks('test').run().assertTasksNotSkipped(':test')
+        //TODO this exposes a possible problem: When changing the test framework stale xml result files from former test framework are still present.
+        executer.withTasks('cleanTest', 'test').run().assertTasksNotSkipped(':cleanTest',':test')
 
-        result = new TestNGExecutionResult(distribution.testDir)
+        result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('TestNGTest')
 
         executer.withTasks('test').run().assertTasksNotSkipped()
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptErrorIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptErrorIntegrationTest.java
index 0b7dffc..e4c7416 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptErrorIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptErrorIntegrationTest.java
@@ -16,8 +16,8 @@
 package org.gradle.integtests;
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest;
-import org.gradle.integtests.fixtures.ExecutionFailure;
-import org.gradle.util.TestFile;
+import org.gradle.integtests.fixtures.executer.ExecutionFailure;
+import org.gradle.test.fixtures.file.TestFile;
 import org.junit.Test;
 
 import static org.hamcrest.Matchers.containsString;
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptExecutionIntegrationTest.groovy
index bb818bb..b8bcbc2 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptExecutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptExecutionIntegrationTest.groovy
@@ -16,17 +16,17 @@
 package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.ArtifactBuilder
-import org.gradle.integtests.fixtures.ExecutionResult
-import org.gradle.util.TestFile
+import org.gradle.integtests.fixtures.executer.ArtifactBuilder
+import org.gradle.integtests.fixtures.executer.ExecutionResult
+import org.gradle.test.fixtures.file.TestFile
 
 class InitScriptExecutionIntegrationTest extends AbstractIntegrationSpec {
     def "executes init.gradle from user home dir"() {
         given:
-        distribution.requireOwnUserHomeDir()
+        executer.requireOwnGradleUserHomeDir()
         
         and:
-        distribution.userHomeDir.file('init.gradle') << 'println "greetings from user home"'
+        gradleUserHomeDir.file('init.gradle') << 'println "greetings from user home"'
 
         when:
         run()
@@ -35,14 +35,18 @@ class InitScriptExecutionIntegrationTest extends AbstractIntegrationSpec {
         output.contains("greetings from user home")
     }
 
+    protected TestFile getGradleUserHomeDir() {
+        new TestFile(executer.gradleUserHomeDir)
+    }
+
     def "executes init scripts from init.d directory in user home dir in alphabetical order"() {
         given:
-        distribution.requireOwnUserHomeDir()
+        executer.requireOwnGradleUserHomeDir()
 
         and:
-        distribution.userHomeDir.file('init.d/a.gradle') << 'println "init #a#"'
-        distribution.userHomeDir.file('init.d/b.gradle') << 'println "init #b#"'
-        distribution.userHomeDir.file('init.d/c.gradle') << 'println "init #c#"'
+        gradleUserHomeDir.file('init.d/a.gradle') << 'println "init #a#"'
+        gradleUserHomeDir.file('init.d/b.gradle') << 'println "init #b#"'
+        gradleUserHomeDir.file('init.d/c.gradle') << 'println "init #c#"'
 
         when:
         run()
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.groovy
index 022f3d6..e38d3e3 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.groovy
@@ -16,9 +16,9 @@
 
 package org.gradle.integtests
 
-import org.gradle.integtests.fixtures.ExecutionFailure
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.util.TestFile
+import org.gradle.integtests.fixtures.executer.ExecutionFailure
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Test
 
 class JavaProjectIntegrationTest extends AbstractIntegrationTest {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MavenProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MavenProjectIntegrationTest.groovy
index f469170..e1315ae 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MavenProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MavenProjectIntegrationTest.groovy
@@ -15,19 +15,15 @@
  */
 package org.gradle.integtests
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.junit.Rule
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.junit.Test
 
-class MavenProjectIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class MavenProjectIntegrationTest extends AbstractIntegrationTest {
 
     @Test
     public void handlesSubProjectsWithoutTheMavenPluginApplied() {
-        dist.testFile("settings.gradle").write("include 'subProject'");
-        dist.testFile("build.gradle") << '''
+        file("settings.gradle").write("include 'subProject'");
+        file("build.gradle") << '''
             apply plugin: 'java'
             apply plugin: 'maven'
         '''
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MultiProjectDependencyIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MultiProjectDependencyIntegrationTest.groovy
index 84943da..c6a48c0 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MultiProjectDependencyIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MultiProjectDependencyIntegrationTest.groovy
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package org.gradle.integtests;
+package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
+import spock.lang.IgnoreIf
 
 import static org.hamcrest.Matchers.containsString
-import spock.lang.IgnoreIf
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
 
 public class MultiProjectDependencyIntegrationTest extends AbstractIntegrationSpec {
 
@@ -179,7 +179,7 @@ project(':c') {
         jarsNotBuilt 'a', 'b', 'c'
     }
 
-    @IgnoreIf({GradleDistributionExecuter.systemPropertyExecuter.executeParallel})  // 'c' + 'd' _may_ be built with parallel executer
+    @IgnoreIf({GradleContextualExecuter.parallel})  // 'c' + 'd' _may_ be built with parallel executer
     def "project dependency a->[b,c] and c->d and b fails"() {
         projectDependency from: 'a', to: ['b', 'c']
         projectDependency from: 'c', to: ['d']
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
index cc8cfa3..9e31b9e 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
@@ -16,22 +16,23 @@
 
 package org.gradle.integtests
 
-import java.util.jar.Manifest
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import static org.junit.Assert.*
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.Sample
+import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.GradleVersion
+import org.junit.Rule
+import org.junit.Test
+
+import java.util.jar.Manifest
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertTrue
 
 /**
  * @author Hans Dockter
  */
-class OsgiProjectSampleIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class OsgiProjectSampleIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final Sample sample = new Sample('osgi')
 
     @Test
@@ -39,7 +40,7 @@ class OsgiProjectSampleIntegrationTest {
         long start = System.currentTimeMillis()
         TestFile osgiProjectDir = sample.dir
         executer.inDirectory(osgiProjectDir).withTasks('clean', 'assemble').run()
-        TestFile tmpDir = dist.testDir
+        TestFile tmpDir = testDirectory
         osgiProjectDir.file('build/libs/osgi-1.0.jar').unzipTo(tmpDir)
         tmpDir.file('META-INF/MANIFEST.MF').withInputStream { InputStream instr ->
             Manifest manifest = new Manifest(instr)
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLayoutIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLayoutIntegrationTest.groovy
index fd5ab76..7c5dd03 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLayoutIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLayoutIntegrationTest.groovy
@@ -15,23 +15,21 @@
  */
 package org.gradle.integtests
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.TestResources
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
 
-class ProjectLayoutIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
+class ProjectLayoutIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final TestResources resources = new TestResources()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
 
     @Test
     public void canHaveSomeSourceAndResourcesInSameDirectoryAndSomeInDifferentDirectories() {
-        dist.testFile('settings.gradle') << 'rootProject.name = "sharedSource"'
-        dist.testFile('build.gradle') << '''
+        file('settings.gradle') << 'rootProject.name = "sharedSource"'
+        file('build.gradle') << '''
 apply plugin: 'java'
 apply plugin: 'groovy'
 apply plugin: 'scala'
@@ -40,9 +38,8 @@ repositories {
     mavenCentral()
 }
 dependencies {
-    groovy 'org.codehaus.groovy:groovy-all:1.8.8'
+    compile 'org.codehaus.groovy:groovy-all:2.0.5'
     // scaladoc in Scala 2.9.2 requires Java 1.6
-    scalaTools 'org.scala-lang:scala-compiler:2.9.1'
     compile 'org.scala-lang:scala-library:2.9.1'
 }
 
@@ -63,26 +60,26 @@ sourceSets.each {
     }
 }
 '''
-        dist.testFile('src/org/gradle/main/resource.txt') << 'some text'
-        dist.testFile('src/org/gradle/test/resource.txt') << 'some text'
-        dist.testFile('src/resources/org/gradle/main/resource2.txt') << 'some text'
-        dist.testFile('src/resources/org/gradle/test/resource2.txt') << 'some text'
-        dist.testFile('src/org/gradle/main/JavaClass.java') << 'package org.gradle; public class JavaClass { }'
-        dist.testFile('src/org/gradle/test/JavaClassTest.java') << 'package org.gradle; class JavaClassTest { JavaClass c = new JavaClass(); }'
-        dist.testFile('src/java/org/gradle/main/JavaClass2.java') << 'package org.gradle; class JavaClass2 { }'
-        dist.testFile('src/java/org/gradle/test/JavaClassTest2.java') << 'package org.gradle; class JavaClassTest2 { JavaClass c = new JavaClass(); }'
-        dist.testFile('src/org/gradle/main/GroovyClass.groovy') << 'package org.gradle; class GroovyClass { }'
-        dist.testFile('src/org/gradle/test/GroovyClassTest.groovy') << 'package org.gradle; class GroovyClassTest { GroovyClass c = new GroovyClass() }'
-        dist.testFile('src/groovy/org/gradle/main/GroovyClass2.groovy') << 'package org.gradle; class GroovyClass2 { }'
-        dist.testFile('src/groovy/org/gradle/test/GroovyClassTest2.groovy') << 'package org.gradle; class GroovyClassTest2 { GroovyClass c = new GroovyClass() }'
-        dist.testFile('src/org/gradle/main/ScalaClass.scala') << 'package org.gradle; class ScalaClass { }'
-        dist.testFile('src/org/gradle/test/ScalaClassTest.scala') << 'package org.gradle; class ScalaClassTest { val c: ScalaClass = new ScalaClass() }'
-        dist.testFile('src/scala/org/gradle/main/ScalaClass2.scala') << 'package org.gradle; class ScalaClass2 { }'
-        dist.testFile('src/scala/org/gradle/test/ScalaClassTest2.scala') << 'package org.gradle; class ScalaClassTest2 { val c: ScalaClass = new ScalaClass() }'
+        file('src/org/gradle/main/resource.txt') << 'some text'
+        file('src/org/gradle/test/resource.txt') << 'some text'
+        file('src/resources/org/gradle/main/resource2.txt') << 'some text'
+        file('src/resources/org/gradle/test/resource2.txt') << 'some text'
+        file('src/org/gradle/main/JavaClass.java') << 'package org.gradle; public class JavaClass { }'
+        file('src/org/gradle/test/JavaClassTest.java') << 'package org.gradle; class JavaClassTest { JavaClass c = new JavaClass(); }'
+        file('src/java/org/gradle/main/JavaClass2.java') << 'package org.gradle; class JavaClass2 { }'
+        file('src/java/org/gradle/test/JavaClassTest2.java') << 'package org.gradle; class JavaClassTest2 { JavaClass c = new JavaClass(); }'
+        file('src/org/gradle/main/GroovyClass.groovy') << 'package org.gradle; class GroovyClass { }'
+        file('src/org/gradle/test/GroovyClassTest.groovy') << 'package org.gradle; class GroovyClassTest { GroovyClass c = new GroovyClass() }'
+        file('src/groovy/org/gradle/main/GroovyClass2.groovy') << 'package org.gradle; class GroovyClass2 { }'
+        file('src/groovy/org/gradle/test/GroovyClassTest2.groovy') << 'package org.gradle; class GroovyClassTest2 { GroovyClass c = new GroovyClass() }'
+        file('src/org/gradle/main/ScalaClass.scala') << 'package org.gradle; class ScalaClass { }'
+        file('src/org/gradle/test/ScalaClassTest.scala') << 'package org.gradle; class ScalaClassTest { val c: ScalaClass = new ScalaClass() }'
+        file('src/scala/org/gradle/main/ScalaClass2.scala') << 'package org.gradle; class ScalaClass2 { }'
+        file('src/scala/org/gradle/test/ScalaClassTest2.scala') << 'package org.gradle; class ScalaClassTest2 { val c: ScalaClass = new ScalaClass() }'
 
         executer.withTasks('build').run()
 
-        File buildDir = dist.testFile('build')
+        File buildDir = file('build')
 
         buildDir.file('classes/main').assertHasDescendants(
                 'org/gradle/JavaClass.class',
@@ -112,7 +109,7 @@ sourceSets.each {
                 'org/gradle/test/resource2.txt',
         )
 
-        TestFile tmpDir = dist.testFile('jarContents')
+        TestFile tmpDir = file('jarContents')
         buildDir.file('libs/sharedSource.jar').unzipTo(tmpDir)
         tmpDir.assertHasDescendants(
                 'META-INF/MANIFEST.MF',
@@ -135,15 +132,15 @@ sourceSets.each {
 
     @Test
     public void multipleProjectsCanShareTheSameSourceDirectory() {
-        dist.testFile('settings.gradle') << 'include "a", "b"'
-        dist.testFile('a/build.gradle') << '''
+        file('settings.gradle') << 'include "a", "b"'
+        file('a/build.gradle') << '''
 apply plugin: 'java'
 sourceSets.main.java {
     srcDirs '../src'
     include 'org/gradle/a/**'
 }
 '''
-        dist.testFile('b/build.gradle') << '''
+        file('b/build.gradle') << '''
 apply plugin: 'java'
 dependencies { compile project(':a') }
 sourceSets.main.java {
@@ -152,15 +149,15 @@ sourceSets.main.java {
 }
 '''
 
-        dist.testFile('src/org/gradle/a/ClassA.java') << 'package org.gradle.a; public class ClassA { }'
-        dist.testFile('src/org/gradle/b/ClassB.java') << 'package org.gradle.b; public class ClassB { private org.gradle.a.ClassA field; }'
+        file('src/org/gradle/a/ClassA.java') << 'package org.gradle.a; public class ClassA { }'
+        file('src/org/gradle/b/ClassB.java') << 'package org.gradle.b; public class ClassB { private org.gradle.a.ClassA field; }'
 
         executer.withTasks('clean', 'assemble').run()
 
-        dist.testFile('a/build/classes/main').assertHasDescendants(
+        file('a/build/classes/main').assertHasDescendants(
                 'org/gradle/a/ClassA.class'
         )
-        dist.testFile('b/build/classes/main').assertHasDescendants(
+        file('b/build/classes/main').assertHasDescendants(
                 'org/gradle/b/ClassB.class'
         )
     }
@@ -169,9 +166,9 @@ sourceSets.main.java {
     public void canUseANonStandardBuildDir() {
         executer.withTasks('build').withArguments('-i').run()
 
-        dist.testFile('build').assertDoesNotExist()
+        file('build').assertDoesNotExist()
 
-        JUnitTestExecutionResult results = new JUnitTestExecutionResult(dist.testFile(), 'target')
+        DefaultTestExecutionResult results = new DefaultTestExecutionResult(file(), 'target')
         results.assertTestClassesExecuted('PersonTest')
         results.testClass('PersonTest').assertTestsExecuted('ok')
     }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLoadingIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLoadingIntegrationTest.java
index b05deeb..4512f37 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLoadingIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLoadingIntegrationTest.java
@@ -16,8 +16,8 @@
 package org.gradle.integtests;
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest;
-import org.gradle.integtests.fixtures.ExecutionFailure;
-import org.gradle.util.TestFile;
+import org.gradle.integtests.fixtures.executer.ExecutionFailure;
+import org.gradle.test.fixtures.file.TestFile;
 import org.junit.Test;
 
 import java.io.File;
@@ -47,7 +47,7 @@ public class ProjectLoadingIntegrationTest extends AbstractIntegrationTest {
 
     @Test
     public void canDetermineRootProjectAndDefaultProjectBasedOnCurrentDirectory() {
-        File rootDir = getTestDir();
+        File rootDir = getTestDirectory();
         File childDir = new File(rootDir, "child");
 
         testFile("settings.gradle").write("include('child')");
@@ -63,7 +63,7 @@ public class ProjectLoadingIntegrationTest extends AbstractIntegrationTest {
 
     @Test
     public void canDetermineRootProjectAndDefaultProjectBasedOnProjectDirectory() {
-        File rootDir = getTestDir();
+        File rootDir = getTestDirectory();
         File childDir = new File(rootDir, "child");
 
         testFile("settings.gradle").write("include('child')");
@@ -104,7 +104,7 @@ public class ProjectLoadingIntegrationTest extends AbstractIntegrationTest {
         ExecutionFailure result = inTestDirectory().withTasks("test").runWithFailure();
         result.assertThatDescription(startsWith("Could not select the default project for this build. Multiple projects in this build have project directory"));
 
-        result = usingProjectDir(getTestDir()).withTasks("test").runWithFailure();
+        result = usingProjectDir(getTestDirectory()).withTasks("test").runWithFailure();
         result.assertThatDescription(startsWith("Could not select the default project for this build. Multiple projects in this build have project directory"));
 
         result = usingBuildFile(testFile("build.gradle")).withTasks("test").runWithFailure();
@@ -153,7 +153,7 @@ public class ProjectLoadingIntegrationTest extends AbstractIntegrationTest {
         buildFile.write("throw new RuntimeException()");
 
         inTestDirectory().withTasks("do-stuff").run();
-        usingProjectDir(getTestDir()).withTasks("do-stuff").run();
+        usingProjectDir(getTestDirectory()).withTasks("do-stuff").run();
     }
 
     @Test
@@ -163,7 +163,7 @@ public class ProjectLoadingIntegrationTest extends AbstractIntegrationTest {
             "project(':child').buildFileName = 'child.gradle'"
         );
 
-        TestFile subDirectory = getTestDir().file("child");
+        TestFile subDirectory = getTestDirectory().file("child");
         subDirectory.file("build.gradle").write("throw new RuntimeException()");
         subDirectory.file("child.gradle").write("task('do-stuff')");
 
@@ -187,7 +187,7 @@ public class ProjectLoadingIntegrationTest extends AbstractIntegrationTest {
         testFile("settings.gradle").write("include 'another'");
         testFile("gradle.properties").writelns("prop=value2", "otherProp=value");
 
-        TestFile subDirectory = getTestDir().file("subdirectory");
+        TestFile subDirectory = getTestDirectory().file("subdirectory");
         TestFile buildFile = subDirectory.file("build.gradle");
         buildFile.writelns("task('do-stuff') << {",
                 "assert prop == 'value'",
@@ -226,7 +226,7 @@ public class ProjectLoadingIntegrationTest extends AbstractIntegrationTest {
 
     @Test
     public void multiProjectBuildCanHaveSettingsFileAndRootBuildFileInSubDir() {
-        TestFile buildFilesDir = getTestDir().file("root");
+        TestFile buildFilesDir = getTestDirectory().file("root");
         TestFile settingsFile = buildFilesDir.file("settings.gradle");
         settingsFile.writelns(
             "includeFlat 'child'",
@@ -240,7 +240,7 @@ public class ProjectLoadingIntegrationTest extends AbstractIntegrationTest {
         TestFile childBuildFile = testFile("child/build.gradle");
         childBuildFile.writelns("task('do-stuff')", "task('task')");
 
-        usingProjectDir(getTestDir()).usingSettingsFile(settingsFile).withTasks("do-stuff").run().assertTasksExecuted(":child:task", ":do-stuff", ":child:do-stuff");
+        usingProjectDir(getTestDirectory()).usingSettingsFile(settingsFile).withTasks("do-stuff").run().assertTasksExecuted(":child:task", ":do-stuff", ":child:do-stuff");
         usingBuildFile(rootBuildFile).withTasks("do-stuff").run().assertTasksExecuted(":child:task", ":do-stuff", ":child:do-stuff");
         usingBuildFile(childBuildFile).usingSettingsFile(settingsFile).withTasks("do-stuff").run().assertTasksExecuted(":child:do-stuff");
     }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptErrorIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptErrorIntegrationTest.java
index daf42f1..078541d 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptErrorIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptErrorIntegrationTest.java
@@ -17,8 +17,8 @@
 package org.gradle.integtests;
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest;
-import org.gradle.integtests.fixtures.ExecutionFailure;
-import org.gradle.util.TestFile;
+import org.gradle.integtests.fixtures.executer.ExecutionFailure;
+import org.gradle.test.fixtures.file.TestFile;
 import org.junit.Test;
 
 import java.io.IOException;
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptExecutionIntegrationTest.groovy
index 8597246..05e2c79 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptExecutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptExecutionIntegrationTest.groovy
@@ -15,13 +15,15 @@
  */
 package org.gradle.integtests
 
-import org.gradle.integtests.fixtures.ArtifactBuilder
-import org.gradle.integtests.fixtures.ExecutionResult
-import org.gradle.util.TestFile
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.executer.ArtifactBuilder
+import org.gradle.integtests.fixtures.executer.ExecutionResult
+import org.gradle.test.fixtures.file.TestFile
+import org.junit.Test
+
+import static org.hamcrest.Matchers.containsString
+import static org.hamcrest.Matchers.not
+import static org.junit.Assert.assertThat
 
 class SettingsScriptExecutionIntegrationTest extends AbstractIntegrationTest {
     @Test
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/StdioIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/StdioIntegrationTest.groovy
index 671d962..88962d1 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/StdioIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/StdioIntegrationTest.groovy
@@ -23,7 +23,7 @@ class StdioIntegrationTest extends AbstractIntegrationSpec {
 
     def "build can read stdin when stdin has bounded length"() {
         given:
-        distribution.requireOwnUserHomeDir()
+        executer.requireOwnGradleUserHomeDir()
         buildFile << '''
 task echo << {
     def reader = new BufferedReader(new InputStreamReader(System.in))
@@ -50,7 +50,7 @@ task echo << {
         def readEnd = new PipedInputStream(writeEnd)
 
         given:
-        distribution.requireOwnUserHomeDir()
+        requireOwnGradleUserHomeDir()
         buildFile << '''
 task echo << {
     def reader = new BufferedReader(new InputStreamReader(System.in))
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskErrorExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskErrorExecutionIntegrationTest.groovy
index d0f4cb1..9c98581 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskErrorExecutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskErrorExecutionIntegrationTest.groovy
@@ -16,8 +16,8 @@
 package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.ExecutionFailure
-import org.gradle.util.TestFile
+import org.gradle.integtests.fixtures.executer.ExecutionFailure
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Test
 
 class TaskErrorExecutionIntegrationTest extends AbstractIntegrationTest {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskExecutionIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskExecutionIntegrationTest.java
index 5e833ba..35c532c 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskExecutionIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskExecutionIntegrationTest.java
@@ -17,7 +17,7 @@
 package org.gradle.integtests;
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest;
-import org.gradle.util.TestFile;
+import org.gradle.test.fixtures.file.TestFile;
 import org.junit.Test;
 import spock.lang.Issue;
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/UnicastMessagingIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/UnicastMessagingIntegrationTest.groovy
deleted file mode 100644
index fbe0d93..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/UnicastMessagingIntegrationTest.groovy
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests
-
-import java.util.concurrent.locks.Condition
-import java.util.concurrent.locks.Lock
-import java.util.concurrent.locks.ReentrantLock
-import org.gradle.api.Action
-import org.gradle.messaging.remote.Address
-import org.gradle.messaging.remote.MessagingClient
-import org.gradle.messaging.remote.MessagingServer
-import org.gradle.messaging.remote.ObjectConnection
-import org.gradle.messaging.remote.internal.MessagingServices
-import org.gradle.util.ConcurrentSpecification
-import java.util.concurrent.CountDownLatch
-import spock.lang.Ignore
-
- at Ignore
-class UnicastMessagingIntegrationTest extends ConcurrentSpecification {
-    def "server can send messages to client"() {
-        RemoteService1 service = Mock()
-        def server = new Server()
-        def client = new Client(server.address)
-
-        when:
-        start {
-            client.addIncoming(service)
-        }
-        start {
-            server.outgoingService1.doStuff("1")
-            server.outgoingService1.doStuff("2")
-            server.outgoingService1.doStuff("3")
-            server.outgoingService1.doStuff("4")
-        }
-        finished()
-        server.stop()
-        client.stop()
-
-        then:
-        1 * service.doStuff("1")
-        1 * service.doStuff("2")
-        1 * service.doStuff("3")
-        1 * service.doStuff("4")
-
-        cleanup:
-        client?.stop()
-        server?.stop()
-    }
-
-    def "client can send messages to server"() {
-        RemoteService1 service = Mock()
-        def server = new Server()
-        def client = new Client(server.address)
-
-        when:
-        start {
-            server.addIncoming(service)
-        }
-        start {
-            client.outgoingService1.doStuff("1")
-            client.outgoingService1.doStuff("2")
-            client.outgoingService1.doStuff("3")
-            client.outgoingService1.doStuff("4")
-        }
-        finished()
-        client.stop()
-        server.stop()
-
-        then:
-        1 * service.doStuff("1")
-        1 * service.doStuff("2")
-        1 * service.doStuff("3")
-        1 * service.doStuff("4")
-
-        cleanup:
-        client?.stop()
-        server?.stop()
-    }
-
-    def "server and client can both send same type of message"() {
-        RemoteService1 serverService = Mock()
-        RemoteService1 clientService = Mock()
-        def received = new CountDownLatch(2)
-        def server = new Server()
-        def client = new Client(server.address)
-
-        when:
-        start {
-            server.addIncoming(serverService)
-            server.outgoingService1.doStuff("from server 1")
-            server.outgoingService1.doStuff("from server 2")
-        }
-        start {
-            client.addIncoming(clientService)
-            client.outgoingService1.doStuff("from client 1")
-            client.outgoingService1.doStuff("from client 2")
-        }
-        received.await()
-        client.stop()
-        server.stop()
-
-        then:
-        1 * serverService.doStuff("from client 1")
-        1 * serverService.doStuff("from client 2") >> { received.countDown() }
-        1 * clientService.doStuff("from server 1")
-        1 * clientService.doStuff("from server 2") >> { received.countDown() }
-
-        cleanup:
-        client?.stop()
-        server?.stop()
-    }
-
-    def "client and server can each send different types of messages"() {
-        RemoteService1 serverService = Mock()
-        RemoteService2 clientService = Mock()
-        def done = new CountDownLatch(2)
-        def server = new Server()
-        def client = new Client(server.address)
-
-        when:
-        start {
-            server.addIncoming(serverService)
-            server.outgoingService2.doStuff("server1")
-            server.outgoingService2.doStuff("server2")
-        }
-        start {
-            client.addIncoming(clientService)
-            client.outgoingService1.doStuff("client1")
-            client.outgoingService1.doStuff("client2")
-        }
-        done.await()
-        client.stop()
-        server.stop()
-
-        then:
-        1 * serverService.doStuff("client1")
-        1 * serverService.doStuff("client2") >> { done.countDown() }
-        1 * clientService.doStuff("server1")
-        1 * clientService.doStuff("server2") >> { done.countDown() }
-
-        cleanup:
-        client?.stop()
-        server?.stop()
-    }
-
-    def "client can start sending before server has started"() {
-        RemoteService1 service = Mock()
-        def server = new Server()
-        def client
-
-        when:
-        start {
-            client = new Client(server.address)
-            client.outgoingService1.doStuff("1")
-            client.outgoingService1.doStuff("2")
-            client.stop()
-        }
-        start {
-            server.addIncoming(service)
-        }
-        finished()
-        server.stop()
-        client?.stop()
-
-        then:
-        1 * service.doStuff("1")
-        1 * service.doStuff("2")
-
-        cleanup:
-        client?.stop()
-        server?.stop()
-    }
-
-    abstract class Participant {
-        final def services = new MessagingServices(getClass().classLoader)
-        private RemoteService1 remoteService1
-        private RemoteService2 remoteService2
-
-        abstract ObjectConnection getConnection()
-
-        void addIncoming(RemoteService1 value) {
-            connection.addIncoming(RemoteService1.class, value)
-        }
-
-        void addIncoming(RemoteService2 value) {
-            connection.addIncoming(RemoteService2.class, value)
-        }
-
-        RemoteService1 getOutgoingService1() {
-            if (remoteService1 == null) {
-                remoteService1 = connection.addOutgoing(RemoteService1)
-            }
-            return remoteService1
-        }
-
-        RemoteService2 getOutgoingService2() {
-            if (remoteService2 == null) {
-                remoteService2 = connection.addOutgoing(RemoteService2)
-            }
-            return remoteService2
-        }
-
-        void stop() {
-            services.stop()
-        }
-    }
-
-    class Server extends Participant {
-        private final Lock lock = new ReentrantLock()
-        private final Condition condition = lock.newCondition()
-        private ObjectConnection connection
-        final Address address
-
-        Server() {
-            address = services.get(MessagingServer.class).accept({ event ->
-                lock.lock()
-                try {
-                    connection = event.connection
-                    condition.signalAll()
-                } finally {
-                    lock.unlock()
-                }
-            } as Action)
-        }
-
-        @Override
-        ObjectConnection getConnection() {
-            lock.lock()
-            try {
-                while (connection == null) {
-                    condition.await()
-                }
-                return connection
-            } finally {
-                lock.unlock()
-            }
-        }
-    }
-
-    class Client extends Participant {
-        final ObjectConnection connection
-
-        Client(Address serverAddress) {
-            connection = services.get(MessagingClient.class).getConnection(serverAddress)
-        }
-    }
-}
-
-interface RemoteService1 {
-    def doStuff(String value)
-}
-
-interface RemoteService2 {
-    def doStuff(String value)
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy
index cc4298d..2efbf39 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy
@@ -16,15 +16,14 @@
 
 package org.gradle.integtests
 
-import org.gradle.integtests.fixtures.ExecutionResult
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
 import org.gradle.integtests.fixtures.Sample
+import org.gradle.integtests.fixtures.executer.*
 import org.gradle.internal.SystemProperties
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import org.junit.Test
 
-import static org.junit.Assert.*
+import static org.junit.Assert.assertEquals
 
 /**
  * @author Hans Dockter
@@ -44,8 +43,10 @@ class WaterProjectIntegrationTest {
     final static String KRILL_NAME = 'krill'
     final static String PHYTOPLANKTON_NAME = 'phytoplankton'
 
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider();
+    private final GradleDistribution dist = new UnderDevelopmentGradleDistribution();
+    private final GradleExecuter executer = new GradleContextualExecuter(dist, temporaryFolder);
+
     @Rule public final Sample sample = new Sample(WATER_NAME)
 
     @Test
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WebProjectIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WebProjectIntegrationTest.java
index d7463ec..d8acaa5 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WebProjectIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WebProjectIntegrationTest.java
@@ -16,7 +16,7 @@
 package org.gradle.integtests;
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest;
-import org.gradle.util.TestFile;
+import org.gradle.test.fixtures.file.TestFile;
 import org.junit.Test;
 
 public class WebProjectIntegrationTest extends AbstractIntegrationTest {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WorkerProcessIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WorkerProcessIntegrationTest.java
deleted file mode 100644
index 27b7332..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WorkerProcessIntegrationTest.java
+++ /dev/null
@@ -1,382 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests;
-
-import org.apache.tools.ant.Project;
-import org.gradle.CacheUsage;
-import org.gradle.api.Action;
-import org.gradle.api.internal.*;
-import org.gradle.api.internal.classpath.DefaultModuleRegistry;
-import org.gradle.api.internal.classpath.ModuleRegistry;
-import org.gradle.api.internal.file.TestFiles;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.cache.CacheRepository;
-import org.gradle.cache.internal.*;
-import org.gradle.internal.id.LongIdGenerator;
-import org.gradle.internal.nativeplatform.ProcessEnvironment;
-import org.gradle.internal.nativeplatform.services.NativeServices;
-import org.gradle.listener.ListenerBroadcast;
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
-import org.gradle.messaging.remote.MessagingServer;
-import org.gradle.messaging.remote.ObjectConnection;
-import org.gradle.messaging.remote.internal.MessagingServices;
-import org.gradle.process.internal.*;
-import org.gradle.process.internal.child.WorkerProcessClassPathProvider;
-import org.gradle.util.TemporaryFolder;
-import org.jmock.Expectations;
-import org.jmock.Sequence;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.*;
-import org.junit.runner.RunWith;
-
-import java.io.ObjectInputStream;
-import java.io.Serializable;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
- at RunWith(JMock.class)
-public class WorkerProcessIntegrationTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final TestListenerInterface listenerMock = context.mock(TestListenerInterface.class);
-    private final MessagingServices messagingServices = new MessagingServices(getClass().getClassLoader());
-    private final MessagingServer server = messagingServices.get(MessagingServer.class);
-    @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
-    private final ProcessMetaDataProvider metaDataProvider = new DefaultProcessMetaDataProvider(NativeServices.getInstance().get(ProcessEnvironment.class));
-    private final CacheFactory factory = new DefaultCacheFactory(new DefaultFileLockManager(metaDataProvider)).create();
-    private final CacheRepository cacheRepository = new DefaultCacheRepository(tmpDir.getDir(), null, CacheUsage.ON, factory);
-    private final ModuleRegistry moduleRegistry = new DefaultModuleRegistry();
-    private final ClassPathRegistry classPathRegistry = new DefaultClassPathRegistry(new DefaultClassPathProvider(moduleRegistry), new WorkerProcessClassPathProvider(cacheRepository, moduleRegistry));
-    private final DefaultWorkerProcessFactory workerFactory = new DefaultWorkerProcessFactory(LogLevel.INFO, server, classPathRegistry, TestFiles.resolver(tmpDir.getTestDir()), new LongIdGenerator());
-    private final ListenerBroadcast<TestListenerInterface> broadcast = new ListenerBroadcast<TestListenerInterface>(
-            TestListenerInterface.class);
-    private final RemoteExceptionListener exceptionListener = new RemoteExceptionListener(broadcast);
-
-    @Before
-    public void setUp() {
-        broadcast.add(listenerMock);
-    }
-
-    @After
-    public void tearDown() {
-        messagingServices.stop();
-    }
-
-    @Test
-    public void workerProcessCanSendMessagesToThisProcess() throws Throwable {
-        context.checking(new Expectations() {{
-            Sequence sequence = context.sequence("sequence");
-            one(listenerMock).send("message 1", 1);
-            inSequence(sequence);
-            one(listenerMock).send("message 2", 2);
-            inSequence(sequence);
-        }});
-
-        execute(worker(new RemoteProcess()));
-    }
-
-    @Test
-    public void thisProcessCanSendEventsToWorkerProcess() throws Throwable {
-        execute(worker(new PingRemoteProcess()).onServer(new Action<ObjectConnection>() {
-            public void execute(ObjectConnection objectConnection) {
-                TestListenerInterface listener = objectConnection.addOutgoing(TestListenerInterface.class);
-                listener.send("1", 0);
-                listener.send("1", 1);
-                listener.send("1", 2);
-                listener.send("stop", 3);
-            }
-        }));
-    }
-
-    @Test
-    public void multipleWorkerProcessesCanSendMessagesToThisProcess() throws Throwable {
-        context.checking(new Expectations() {{
-            Sequence process1 = context.sequence("sequence1");
-            one(listenerMock).send("message 1", 1);
-            inSequence(process1);
-            one(listenerMock).send("message 2", 2);
-            inSequence(process1);
-            Sequence process2 = context.sequence("sequence2");
-            one(listenerMock).send("other 1", 1);
-            inSequence(process2);
-            one(listenerMock).send("other 2", 2);
-            inSequence(process2);
-        }});
-
-        execute(worker(new RemoteProcess()), worker(new OtherRemoteProcess()));
-    }
-
-    @Test
-    @Ignore
-    public void handlesWorkerProcessWhichCrashes() throws Throwable {
-        context.checking(new Expectations() {{
-            atMost(1).of(listenerMock).send("message 1", 1);
-            atMost(1).of(listenerMock).send("message 2", 2);
-        }});
-
-        execute(worker(new CrashingRemoteProcess()).expectStopFailure());
-    }
-
-    @Test
-    public void handlesWorkerActionWhichThrowsException() throws Throwable {
-        execute(worker(new BrokenRemoteProcess()).expectStopFailure());
-    }
-
-    @Test
-    public void handlesWorkerActionThatLeavesThreadsRunning() throws Throwable {
-        context.checking(new Expectations() {{
-            one(listenerMock).send("message 1", 1);
-            one(listenerMock).send("message 2", 2);
-        }});
-
-        execute(worker(new NoCleanUpRemoteProcess()));
-    }
-
-    @Test
-    public void handlesWorkerProcessWhichNeverConnects() throws Throwable {
-        execute(worker(new NoConnectRemoteProcess()).expectStartFailure());
-    }
-
-    @Test
-    public void handlesWorkerProcessWhenJvmFailsToStart() throws Throwable {
-        execute(worker(Actions.doNothing()).jvmArgs("--broken").expectStartFailure());
-    }
-
-    private ChildProcess worker(Action<? super WorkerProcessContext> action) {
-        return new ChildProcess(action);
-    }
-
-    void execute(ChildProcess... processes) throws Throwable {
-        for (ChildProcess process : processes) {
-            process.start();
-        }
-        for (ChildProcess process : processes) {
-            process.waitForStop();
-        }
-        messagingServices.stop();
-        exceptionListener.rethrow();
-    }
-
-    private class ChildProcess {
-        private boolean stopFails;
-        private boolean startFails;
-        private WorkerProcess proc;
-        private Action<? super WorkerProcessContext> action;
-        private List<String> jvmArgs = Collections.emptyList();
-        private Action<ObjectConnection> serverAction;
-
-        public ChildProcess(Action<? super WorkerProcessContext> action) {
-            this.action = action;
-        }
-
-        ChildProcess expectStopFailure() {
-            stopFails = true;
-            return this;
-        }
-
-        ChildProcess expectStartFailure() {
-            startFails = true;
-            return this;
-        }
-
-        public void start() {
-            WorkerProcessBuilder builder = workerFactory.create();
-            builder.applicationClasspath(classPathRegistry.getClassPath("ANT").getAsFiles());
-            builder.sharedPackages("org.apache.tools.ant");
-            builder.getJavaCommand().systemProperty("test.system.property", "value");
-            builder.getJavaCommand().environment("TEST_ENV_VAR", "value");
-            builder.worker(action);
-
-            builder.getJavaCommand().jvmArgs(jvmArgs);
-
-            proc = builder.build();
-            try {
-                proc.start();
-                assertFalse(startFails);
-            } catch (ExecException e) {
-                assertTrue(startFails);
-                return;
-            }
-            proc.getConnection().addIncoming(TestListenerInterface.class, exceptionListener);
-            if (serverAction != null) {
-                serverAction.execute(proc.getConnection());
-            }
-        }
-
-        public void waitForStop() {
-            if (startFails) {
-                return;
-            }
-            try {
-                proc.waitForStop();
-                assertFalse("Expected process to fail", stopFails);
-            } catch (ExecException e) {
-                assertTrue("Unexpected failure in worker process", stopFails);
-            }
-        }
-
-        public ChildProcess onServer(Action<ObjectConnection> action) {
-            this.serverAction = action;
-            return this;
-        }
-
-        public ChildProcess jvmArgs(String... jvmArgs) {
-            this.jvmArgs = Arrays.asList(jvmArgs);
-            return this;
-        }
-    }
-
-    public static class RemoteExceptionListener implements Dispatch<MethodInvocation> {
-        Throwable ex;
-        final Dispatch<MethodInvocation> dispatch;
-
-        public RemoteExceptionListener(Dispatch<MethodInvocation> dispatch) {
-            this.dispatch = dispatch;
-        }
-
-        public void dispatch(MethodInvocation message) {
-            try {
-                dispatch.dispatch(message);
-            } catch (Throwable e) {
-                ex = e;
-            }
-        }
-
-        public void rethrow() throws Throwable {
-            if (ex != null) {
-                throw ex;
-            }
-        }
-    }
-
-    public static class RemoteProcess implements Action<WorkerProcessContext>, Serializable {
-        public void execute(WorkerProcessContext workerProcessContext) {
-            // Check environment
-            assertThat(System.getProperty("test.system.property"), equalTo("value"));
-            assertThat(System.getenv().get("TEST_ENV_VAR"), equalTo("value"));
-
-            // Check ClassLoaders
-            ClassLoader antClassLoader = Project.class.getClassLoader();
-            ClassLoader thisClassLoader = getClass().getClassLoader();
-            ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
-
-            assertThat(antClassLoader, not(sameInstance(systemClassLoader)));
-            assertThat(thisClassLoader, not(sameInstance(systemClassLoader)));
-            assertThat(antClassLoader.getParent(), equalTo(systemClassLoader.getParent()));
-            try {
-                assertThat(thisClassLoader.loadClass(Project.class.getName()), sameInstance((Object) Project.class));
-            } catch (ClassNotFoundException e) {
-                throw new RuntimeException(e);
-            }
-
-            // Send some messages
-            TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(
-                    TestListenerInterface.class);
-            sender.send("message 1", 1);
-            sender.send("message 2", 2);
-        }
-    }
-
-    public static class OtherRemoteProcess implements Action<WorkerProcessContext>, Serializable {
-        public void execute(WorkerProcessContext workerProcessContext) {
-            TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(TestListenerInterface.class);
-            sender.send("other 1", 1);
-            sender.send("other 2", 2);
-        }
-    }
-
-    public static class NoCleanUpRemoteProcess implements Action<WorkerProcessContext>, Serializable {
-        public void execute(WorkerProcessContext workerProcessContext) {
-            final Lock lock = new ReentrantLock();
-            lock.lock();
-            new Thread(new Runnable() {
-                public void run() {
-                    lock.lock();
-                }
-            }).start();
-
-            TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(
-                    TestListenerInterface.class);
-            sender.send("message 1", 1);
-            sender.send("message 2", 2);
-        }
-    }
-
-    public static class PingRemoteProcess implements Action<WorkerProcessContext>, Serializable, TestListenerInterface {
-        CountDownLatch stopReceived;
-        int count;
-
-        public void send(String message, int count) {
-            assertEquals(this.count, count);
-            this.count++;
-            if (message.equals("stop")) {
-                assertEquals(4, this.count);
-                stopReceived.countDown();
-            }
-        }
-
-        public void execute(WorkerProcessContext workerProcessContext) {
-            stopReceived = new CountDownLatch(1);
-            workerProcessContext.getServerConnection().addIncoming(TestListenerInterface.class, this);
-            try {
-                stopReceived.await();
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
-    public static class CrashingRemoteProcess implements Action<WorkerProcessContext>, Serializable {
-        public void execute(WorkerProcessContext workerProcessContext) {
-            TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(TestListenerInterface.class);
-            sender.send("message 1", 1);
-            sender.send("message 2", 2);
-            // crash
-            Runtime.getRuntime().halt(1);
-        }
-    }
-
-    public static class BrokenRemoteProcess implements Action<WorkerProcessContext>, Serializable {
-        public void execute(WorkerProcessContext workerProcessContext) {
-            throw new RuntimeException("broken");
-        }
-    }
-
-    public static class NoConnectRemoteProcess implements Action<WorkerProcessContext>, Serializable {
-        private void readObject(ObjectInputStream instr) {
-            System.exit(0);
-        }
-
-        public void execute(WorkerProcessContext workerProcessContext) {
-            throw new UnsupportedOperationException();
-        }
-    }
-
-    public interface TestListenerInterface {
-        public void send(String message, int count);
-    }
-}
-
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/environment/BuildEnvironmentIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/environment/BuildEnvironmentIntegrationTest.groovy
index 969c74a..7535154 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/environment/BuildEnvironmentIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/environment/BuildEnvironmentIntegrationTest.groovy
@@ -34,7 +34,7 @@ class BuildEnvironmentIntegrationTest extends AbstractIntegrationSpec {
         testProject()
 
         when:
-        def relativeDir = new File(distribution.testDir, 'java/multiproject/../quickstart')
+        def relativeDir = new File(testDirectory, 'java/multiproject/../quickstart')
         executer.inDirectory(relativeDir).run()
 
         then:
@@ -46,7 +46,7 @@ class BuildEnvironmentIntegrationTest extends AbstractIntegrationSpec {
         testProject()
 
         when:
-        def mixedCaseDir = new File(distribution.testDir, "JAVA/QuickStart")
+        def mixedCaseDir = new File(testDirectory, "JAVA/QuickStart")
         executer.inDirectory(mixedCaseDir).run()
 
         then:
@@ -58,7 +58,7 @@ class BuildEnvironmentIntegrationTest extends AbstractIntegrationSpec {
         testProject()
 
         when:
-        def shortDir = new File(distribution.testDir, 'java/QUICKS~1')
+        def shortDir = new File(testDirectory, 'java/QUICKS~1')
         executer.inDirectory(shortDir).run()
 
         then:
@@ -66,8 +66,8 @@ class BuildEnvironmentIntegrationTest extends AbstractIntegrationSpec {
     }
 
     private testProject() {
-        distribution.testFile("java/multiproject").createDir()
-        def projectDir = distribution.testFile("java/quickstart")
+        file("java/multiproject").createDir()
+        def projectDir = file("java/quickstart")
         projectDir.file('build.gradle') << "assert file('.') == new File(new URI('${projectDir.toURI()}'))"
     }
 
@@ -89,8 +89,8 @@ class BuildEnvironmentIntegrationTest extends AbstractIntegrationSpec {
     }
 
     def "build is executed with working directory set to where the build was launched from"() {
-        def project1 = distribution.testFile("project1")
-        def project2 = distribution.testFile("project2")
+        def project1 = file("project1")
+        def project2 = file("project2")
 
         project1.file('build.gradle') << """
 def expectedDir = new File(new URI('${project1.toURI()}'))
@@ -159,7 +159,7 @@ assert classesDir.directory
 
         when:
         // Need the forking executer for this to work. Embedded executer will not fork a new process if jvm doesn't match.
-        def out = executer.withForkingExecuter().run().output
+        def out = executer.requireGradleHome(true).run().output
 
         then:
         out.contains("javaHome=" + alternateJavaHome.canonicalPath)
@@ -174,7 +174,7 @@ assert System.getProperty('some-prop') == 'some-value'
 """
 
         when:
-        executer.withForkingExecuter().withNoDefaultJvmArgs().run()
+        executer.requireGradleHome(true).withNoDefaultJvmArgs().run()
 
         then:
         noExceptionThrown()
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/DistroTempDirIsUniquePerTestSpec.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/DistroTempDirIsUniquePerTestSpec.groovy
deleted file mode 100644
index 8ac5e3f..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/DistroTempDirIsUniquePerTestSpec.groovy
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package org.gradle.integtests.fixture
-
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.junit.Rule
-import spock.lang.Specification
-
-/**
- * by Szczepan Faber, created at: 3/14/12
- */
-class DistroTempDirIsUniquePerTestSpec extends Specification {
-
-    @Rule GradleDistribution dist = new GradleDistribution();
-    static tests = new HashSet()
-    static tmpDirs = new HashSet()
-
-    def setup() {
-        //it's very important we try to access the test dir in the setup()
-        dist.testDir
-    }
-    
-    def "testOne"() {
-        when:
-        tests << "testOne"
-        tmpDirs << dist.testDir
-        
-        then:
-        tests.size() == tmpDirs.size()
-    }
-
-    def "testTwo"() {
-        when:
-        tests << "testTwo"
-        tmpDirs << dist.testDir
-
-        then:
-        tests.size() == tmpDirs.size()
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/M2Installation.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/M2Installation.groovy
deleted file mode 100644
index 7e0fcfe..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/M2Installation.groovy
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixture
-
-import org.gradle.integtests.fixtures.MavenFileRepository
-import org.gradle.util.TestFile
-
-class M2Installation {
-    final TestFile userHomeDir
-    final TestFile userM2Directory
-    final TestFile userSettingsFile
-    final TestFile globalMavenDirectory
-
-    public M2Installation(TestFile m2Directory) {
-        userHomeDir = m2Directory.createDir("maven_home")
-        userM2Directory = userHomeDir.createDir(".m2")
-        userSettingsFile = userM2Directory.file("settings.xml")
-        globalMavenDirectory = userHomeDir.createDir("m2_home")
-    }
-
-    MavenFileRepository mavenRepo() {
-        new MavenFileRepository(userM2Directory.file("repository"))
-    }
-
-    M2Installation generateUserSettingsFile(MavenFileRepository userRepository) {
-        userSettingsFile.text = """
-<settings>
-    <localRepository>${userRepository.rootDir.absolutePath}</localRepository>
-</settings>"""
-        return this
-    }
-
-    M2Installation generateGlobalSettingsFile(MavenFileRepository globalRepository = mavenRepo()) {
-        def settings = globalMavenDirectory.file("conf/settings.xml").createFile()
-        settings.text = """
-<settings>
-    <localRepository>${globalRepository.rootDir.absolutePath}</localRepository>
-</settings>"""
-        return this
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/TempDirIsUniquePerTestSpec.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/TempDirIsUniquePerTestSpec.groovy
index f6ce1bd..517a83e 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/TempDirIsUniquePerTestSpec.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/TempDirIsUniquePerTestSpec.groovy
@@ -13,9 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+
 package org.gradle.integtests.fixture
 
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -24,19 +26,19 @@ import spock.lang.Specification
  */
 class TempDirIsUniquePerTestSpec extends Specification {
 
-    @Rule TemporaryFolder temp = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider tmp = new TestNameTestDirectoryProvider();
     static tests = new HashSet()
     static tmpDirs = new HashSet()
 
     def setup() {
         //it's very important we try to access the test dir in the setup()
-        temp.testDir
+        tmp.testDirectory
     }
     
     def "testOne"() {
         when:
         tests << "testOne"
-        tmpDirs << temp.testDir
+        tmpDirs << tmp.testDirectory
         
         then:
         tests.size() == tmpDirs.size()
@@ -45,7 +47,7 @@ class TempDirIsUniquePerTestSpec extends Specification {
     def "testTwo"() {
         when:
         tests << "testTwo"
-        tmpDirs << temp.testDir
+        tmpDirs << tmp.testDirectory
 
         then:
         tests.size() == tmpDirs.size()
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggingIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggingIntegrationTest.groovy
index 0c861c3..a19ee7b 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggingIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggingIntegrationTest.groovy
@@ -16,18 +16,21 @@
 
 package org.gradle.integtests.logging
 
-import org.gradle.util.TestFile
-import org.gradle.integtests.fixtures.*
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.UsesSample
+import org.gradle.integtests.fixtures.executer.ExecutionResult
 import org.gradle.internal.SystemProperties
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
 
 /**
  * @author Hans Dockter
  */
-class LoggingIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class LoggingIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final TestResources resources = new TestResources()
     @Rule public final Sample sampleResources = new Sample()
 
@@ -216,7 +219,7 @@ class LoggingIntegrationTest {
 
     def run(LogLevel level) {
         resources.maybeCopy('LoggingIntegrationTest/logging')
-        TestFile loggingDir = dist.testDir
+        TestFile loggingDir = testDirectory
         loggingDir.file("buildSrc/build/.gradle").deleteDir()
         loggingDir.file("nestedBuild/buildSrc/.gradle").deleteDir()
 
@@ -226,7 +229,7 @@ class LoggingIntegrationTest {
     }
 
     def runBroken(LogLevel level) {
-        TestFile loggingDir = dist.testDir
+        TestFile loggingDir = testDirectory
 
         return executer.setAllowExtraLogging(false).inDirectory(loggingDir).withTasks('broken').runWithFailure()
     }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyHttpPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyHttpPublishIntegrationTest.groovy
index a0877b3..7ab7452 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyHttpPublishIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyHttpPublishIntegrationTest.groovy
@@ -18,13 +18,13 @@
 package org.gradle.integtests.publish.ivy
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.ProgressLoggingFixture
+import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
+import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.ivy.IvyFileModule
 import org.gradle.test.fixtures.ivy.IvyModule
 import org.gradle.test.fixtures.server.http.HttpServer
 import org.gradle.util.GradleVersion
 import org.gradle.util.Jvm
-import org.gradle.util.TestFile
 import org.gradle.util.TextUtil
 import org.hamcrest.Matchers
 import org.junit.Rule
@@ -164,7 +164,7 @@ uploadArchives {
 
         and:
         failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
-        failure.assertHasCause('Could not publish configuration: [archives]')
+        failure.assertHasCause('Could not publish configuration \'archives\'')
         failure.assertThatCause(Matchers.containsString('Received status code 401 from server: Unauthorized'))
 
         where:
@@ -199,7 +199,7 @@ uploadArchives {
 
         and:
         failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
-        failure.assertHasCause('Could not publish configuration: [archives]')
+        failure.assertHasCause('Could not publish configuration \'archives\'')
         failure.assertThatCause(Matchers.containsString('Received status code 500 from server: broken'))
 
         when:
@@ -210,7 +210,7 @@ uploadArchives {
 
         and:
         failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
-        failure.assertHasCause('Could not publish configuration: [archives]')
+        failure.assertHasCause('Could not publish configuration \'archives\'')
         failure.assertHasCause("org.apache.http.conn.HttpHostConnectException: Connection to ${repositoryUrl} refused")
     }
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySFtpPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySFtpPublishIntegrationTest.groovy
index 4871778..68be4c8 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySFtpPublishIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySFtpPublishIntegrationTest.groovy
@@ -16,14 +16,14 @@
 package org.gradle.integtests.publish.ivy
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.ProgressLoggingFixture
+import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
 import org.gradle.test.fixtures.server.sftp.SFTPServer
 import org.junit.Rule
 
 class IvySFtpPublishIntegrationTest extends AbstractIntegrationSpec {
 
     @Rule
-    public final SFTPServer sftpServer = new SFTPServer(distribution.temporaryFolder)
+    public final SFTPServer sftpServer = new SFTPServer(this)
     @Rule
     ProgressLoggingFixture progressLogging
 
@@ -86,7 +86,7 @@ class IvySFtpPublishIntegrationTest extends AbstractIntegrationSpec {
 
         then:
         failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
-        failure.assertHasCause('Could not publish configuration: [archives]')
+        failure.assertHasCause('Could not publish configuration \'archives\'')
         failure.assertHasCause("java.io.IOException: Auth fail")
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/SamplesIvyPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/SamplesIvyPublishIntegrationTest.groovy
index cdaf94d..4902ea05 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/SamplesIvyPublishIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/SamplesIvyPublishIntegrationTest.groovy
@@ -15,8 +15,7 @@
  */
 package org.gradle.integtests.publish.ivy
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.Sample
 import org.junit.Rule
 import org.junit.Test
@@ -24,11 +23,8 @@ import org.junit.Test
 /**
  * @author Hans Dockter
  */
-public class SamplesIvyPublishIntegrationTest {
-    @Rule
-    public final GradleDistribution dist = new GradleDistribution()
-    @Rule
-    public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+public class SamplesIvyPublishIntegrationTest extends AbstractIntegrationTest {
+
     @Rule
     public final Sample sample = new Sample("ivypublish")
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenJavaProjectPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenJavaProjectPublishIntegrationTest.groovy
index 8d31424..bb54f80 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenJavaProjectPublishIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenJavaProjectPublishIntegrationTest.groovy
@@ -54,7 +54,7 @@ uploadArchives {
         then:
         def mavenModule = mavenRepo.module("org.gradle.test", "publishTest", "1.9")
         mavenModule.assertArtifactsPublished("publishTest-1.9.pom", "publishTest-1.9.jar")
-        mavenModule.pom.scopes.compile.assertDependsOn("commons-collections", "commons-collections", "3.2.1")
-        mavenModule.pom.scopes.runtime.assertDependsOn("commons-io", "commons-io", "1.4")
+        mavenModule.parsedPom.scopes.compile.assertDependsOn("commons-collections", "commons-collections", "3.2.1")
+        mavenModule.parsedPom.scopes.runtime.assertDependsOn("commons-io", "commons-io", "1.4")
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenMultiProjectPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenMultiProjectPublishIntegrationTest.groovy
index 953fb7d..957ee83 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenMultiProjectPublishIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenMultiProjectPublishIntegrationTest.groovy
@@ -35,7 +35,7 @@ project(":project1") {
         run ":project1:uploadArchives"
 
         then:
-        def pom = mavenModule.pom
+        def pom = mavenModule.parsedPom
         pom.scopes.compile.assertDependsOn("org.gradle.test", "project2", "1.9")
     }
 
@@ -57,7 +57,7 @@ project(":project2") {
         run ":project1:uploadArchives"
 
         then:
-        def pom = mavenModule.pom
+        def pom = mavenModule.parsedPom
         pom.scopes.compile.assertDependsOn("org.gradle.test", "changed", "1.9")
     }
 
@@ -83,7 +83,7 @@ project(":project2") {
         run ":project1:uploadArchives"
 
         then:
-        def pom = mavenModule.pom
+        def pom = mavenModule.parsedPom
         pom.scopes.compile.assertDependsOn("org.gradle.test", "changed", "1.9")
     }
 
@@ -110,7 +110,7 @@ project(":project2") {
         run ":project1:uploadArchives"
 
         then:
-        def pom = mavenModule.pom
+        def pom = mavenModule.parsedPom
         pom.scopes.compile.assertDependsOn("org.gradle.test", "project2", "1.9")
     }
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPomGenerationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPomGenerationIntegrationTest.groovy
index a17b1de..7715115 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPomGenerationIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPomGenerationIntegrationTest.groovy
@@ -48,7 +48,7 @@ uploadArchives {
 
         then:
         def mavenModule = mavenRepo.module("org.gradle.test", pomArtifactId, pomVersion)
-        def pom = mavenModule.pom
+        def pom = mavenModule.parsedPom
         pom.groupId == "org.gradle.test"
         pom.artifactId == pomArtifactId
         pom.version == pomVersion
@@ -92,7 +92,7 @@ uploadArchives {
 
         then:
         def mavenModule = mavenRepo.module(pomGroupId, pomArtifactId, pomVersion)
-        def pom = mavenModule.pom
+        def pom = mavenModule.parsedPom
         pom.groupId == pomGroupId
         pom.artifactId == pomArtifactId
         pom.version == pomVersion
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishRespectsPomConfigurationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishRespectsPomConfigurationTest.groovy
index aed7547..bc44445 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishRespectsPomConfigurationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishRespectsPomConfigurationTest.groovy
@@ -24,7 +24,7 @@ class MavenPublishRespectsPomConfigurationTest extends AbstractIntegrationSpec {
     @Ignore
     def "project dependencies in pom respect renamed artifactId"() {
         setup:
-        def root = distribution.testFile("root")
+        def root = file("root")
         root.file("settings.gradle") << """
     rootProject.name = "publish"
     include 'project1'
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenPomGenerationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenPomGenerationIntegrationTest.groovy
index 72b205c..0c30ded 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenPomGenerationIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenPomGenerationIntegrationTest.groovy
@@ -21,9 +21,9 @@ import org.custommonkey.xmlunit.XMLAssert
 import org.custommonkey.xmlunit.examples.RecursiveElementNameAndTextQualifier
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.Resources
 import org.gradle.internal.SystemProperties
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.Resources
 import org.hamcrest.Matchers
 import org.junit.Assert
 import org.junit.Before
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenQuickstartIntegrationTest.groovy
index df2c8a6..6e8ef36 100755
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenQuickstartIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenQuickstartIntegrationTest.groovy
@@ -21,9 +21,9 @@ import org.custommonkey.xmlunit.XMLAssert
 import org.custommonkey.xmlunit.examples.RecursiveElementNameAndTextQualifier
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.Resources
 import org.gradle.internal.SystemProperties
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.Resources
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/AbstractDependencyResolutionTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/AbstractDependencyResolutionTest.groovy
deleted file mode 100644
index 4063311..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/AbstractDependencyResolutionTest.groovy
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.resolve
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.MavenFileRepository
-import org.gradle.integtests.fixtures.MavenHttpRepository
-import org.gradle.test.fixtures.ivy.IvyFileRepository
-import org.gradle.test.fixtures.ivy.IvyHttpRepository
-import org.gradle.test.fixtures.server.http.HttpServer
-import org.gradle.util.GradleVersion
-import org.junit.Rule
-
-import static org.gradle.test.matchers.UserAgentMatcher.matchesNameAndVersion
-
-abstract class AbstractDependencyResolutionTest extends AbstractIntegrationSpec {
-    @Rule public final HttpServer server = new HttpServer()
-
-    def "setup"() {
-        server.expectUserAgent(matchesNameAndVersion("Gradle", GradleVersion.current().getVersion()))
-        requireOwnUserHomeDir()
-    }
-
-    IvyFileRepository ivyRepo(def dir = 'ivy-repo') {
-        return ivy(dir)
-    }
-
-    IvyHttpRepository getIvyHttpRepo() {
-        return new IvyHttpRepository(server, "/repo", ivyRepo)
-    }
-
-    IvyHttpRepository ivyHttpRepo(String name) {
-        assert !name.startsWith("/")
-        return new IvyHttpRepository(server, "/${name}", ivyRepo(name))
-    }
-
-    MavenFileRepository mavenRepo(String name = "repo") {
-        return maven(name)
-    }
-
-    MavenHttpRepository getMavenHttpRepo() {
-        return new MavenHttpRepository(server, "/repo", mavenRepo)
-    }
-
-    MavenHttpRepository mavenHttpRepo(String name) {
-        assert !name.startsWith("/")
-        return new MavenHttpRepository(server, "/${name}", mavenRepo(name))
-    }
-
-    MavenHttpRepository mavenHttpRepo(String contextPath, MavenFileRepository backingRepo) {
-        return new MavenHttpRepository(server, contextPath, backingRepo)
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy
deleted file mode 100644
index c0357c4..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy
+++ /dev/null
@@ -1,659 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve
-
-import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.ExecutionFailure
-import org.gradle.integtests.fixtures.TestResources
-import org.gradle.util.TestFile
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import spock.lang.Issue
-
-import static org.hamcrest.Matchers.containsString
-import static org.hamcrest.Matchers.startsWith
-
-class ArtifactDependenciesIntegrationTest extends AbstractIntegrationTest {
-    @Rule
-    public final TestResources testResources = new TestResources()
-
-    @Before
-    public void setup() {
-        distribution.requireOwnUserHomeDir()
-    }
-
-    @Test
-    public void canHaveConfigurationHierarchy() {
-        File buildFile = testFile("projectWithConfigurationHierarchy.gradle");
-        usingBuildFile(buildFile).run();
-    }
-
-    @Test
-    public void dependencyReportWithConflicts() {
-        File buildFile = testFile("projectWithConflicts.gradle");
-        usingBuildFile(buildFile).run();
-        usingBuildFile(buildFile).withDependencyList().run();
-    }
-
-    @Test
-    public void canNestModules() throws IOException {
-        File buildFile = testFile("projectWithNestedModules.gradle");
-        usingBuildFile(buildFile).run();
-    }
-
-    @Test
-    public void canHaveCycleInDependencyGraph() throws IOException {
-        File buildFile = testFile("projectWithCyclesInDependencyGraph.gradle");
-        usingBuildFile(buildFile).run();
-    }
-
-    @Test
-    public void canUseDynamicVersions() throws IOException {
-        File buildFile = testFile("projectWithDynamicVersions.gradle");
-        usingBuildFile(buildFile).run();
-    }
-
-    @Test
-    public void resolutionFailsWhenProjectHasNoRepositoriesEvenWhenArtifactIsCachedLocally() {
-        testFile('settings.gradle') << 'include "a", "b"'
-        testFile('build.gradle') << """
-subprojects {
-    configurations {
-        compile
-    }
-    task listDeps << { configurations.compile.each { } }
-}
-project(':a') {
-    repositories {
-        maven { url '${repo.uri}' }
-    }
-    dependencies {
-        compile 'org.gradle.test:external1:1.0'
-    }
-}
-project(':b') {
-    dependencies {
-        compile 'org.gradle.test:external1:1.0'
-    }
-}
-"""
-        repo.module('org.gradle.test', 'external1', '1.0').publish()
-
-        inTestDirectory().withTasks('a:listDeps').run()
-        def result = inTestDirectory().withTasks('b:listDeps').runWithFailure()
-        result.assertThatCause(containsString('Could not find group:org.gradle.test, module:external1, version:1.0.'))
-    }
-
-    @Test
-    public void resolutionFailsForMissingArtifact() {
-        testFile('build.gradle') << """
-repositories {
-    maven { url '${repo.uri}' }
-}
-configurations {
-    compile; missingExt; missingClassifier
-}
-dependencies {
-    compile "org.gradle.test:lib:1.0"
-    missingExt "org.gradle.test:lib:1.0 at zip"
-    missingClassifier "org.gradle.test:lib:1.0:classifier1"
-}
-task listJar << { configurations.compile.each { } }
-task listMissingExt << { configurations.missingExt.each { } }
-task listMissingClassifier << { configurations.missingClassifier.each { } }
-"""
-        repo.module('org.gradle.test', 'lib', '1.0').publish()
-
-        inTestDirectory().withTasks('listJar').run()
-
-        def result = inTestDirectory().withTasks('listMissingExt').runWithFailure()
-        result.assertThatCause(containsString("Artifact 'org.gradle.test:lib:1.0 at zip' not found"))
-
-        result = inTestDirectory().withTasks('listMissingClassifier').runWithFailure()
-        result.assertThatCause(containsString("Artifact 'org.gradle.test:lib:1.0:classifier1 at jar' not found"))
-    }
-
-    @Test
-    @Issue("GRADLE-1342")
-    public void resolutionDoesNotUseCachedArtifactFromDifferentRepository() {
-        def repo1 = maven('repo1')
-        repo1.module('org.gradle.test', 'external1', '1.0').publish()
-        def repo2 = maven('repo2')
-
-        testFile('settings.gradle') << 'include "a", "b"'
-        testFile('build.gradle') << """
-subprojects {
-    configurations {
-        compile
-    }
-    task listDeps << { configurations.compile.each { } }
-}
-project(':a') {
-    repositories {
-        maven { url '${repo1.uri}' }
-    }
-    dependencies {
-        compile 'org.gradle.test:external1:1.0'
-    }
-}
-project(':b') {
-    repositories {
-        maven { url '${repo2.uri}' }
-    }
-    dependencies {
-        compile 'org.gradle.test:external1:1.0'
-    }
-}
-"""
-
-        inTestDirectory().withTasks('a:listDeps').run()
-        def result = inTestDirectory().withTasks('b:listDeps').runWithFailure()
-        result.assertThatCause(containsString('Could not find group:org.gradle.test, module:external1, version:1.0.'))
-    }
-
-    @Test
-    public void exposesMetaDataAboutResolvedArtifactsInAFixedOrder() {
-        def module = repo.module('org.gradle.test', 'lib', '1.0')
-        module.artifact(type: 'zip')
-        module.artifact(classifier: 'classifier')
-        module.publish()
-        repo.module('org.gradle.test', 'dist', '1.0').hasType('zip').publish()
-
-        testFile('build.gradle') << """
-repositories {
-    maven { url '${repo.uri}' }
-}
-configurations {
-    compile
-}
-dependencies {
-    compile "org.gradle.test:lib:1.0"
-    compile "org.gradle.test:lib:1.0:classifier"
-    compile "org.gradle.test:lib:1.0 at zip"
-    compile "org.gradle.test:dist:1.0"
-}
-task test << {
-    assert configurations.compile.files.collect { it.name } == ['lib-1.0.jar', 'lib-1.0.zip', 'lib-1.0-classifier.jar', 'dist-1.0.zip']
-    def artifacts = configurations.compile.resolvedConfiguration.resolvedArtifacts as List
-    assert artifacts.size() == 4
-    assert artifacts[0].name == 'lib'
-    assert artifacts[0].type == 'jar'
-    assert artifacts[0].extension == 'jar'
-    assert artifacts[0].classifier == null
-    assert artifacts[1].name == 'lib'
-    assert artifacts[1].type == 'jar'
-    assert artifacts[1].extension == 'jar'
-    assert artifacts[1].classifier == 'classifier'
-    assert artifacts[2].name == 'lib'
-    assert artifacts[2].type == 'zip'
-    assert artifacts[2].extension == 'zip'
-    assert artifacts[2].classifier == null
-    assert artifacts[3].name == 'dist'
-    assert artifacts[3].type == 'zip'
-    assert artifacts[3].extension == 'zip'
-    assert artifacts[3].classifier == null
-}
-"""
-
-        executer.withDeprecationChecksDisabled()
-        def result = inTestDirectory().withTasks('test').run()
-        assert result.output.contains('Relying on packaging to define the extension of the main artifact has been deprecated')
-    }
-
-    @Test
-    @Issue("GRADLE-1567")
-    public void resolutionDifferentiatesBetweenArtifactsThatDifferOnlyInClassifier() {
-        def module = repo.module('org.gradle.test', 'external1', '1.0')
-        module.artifact(classifier: 'classifier1')
-        module.artifact(classifier: 'classifier2')
-        module.publish()
-
-        testFile('settings.gradle') << 'include "a", "b", "c"'
-        testFile('build.gradle') << """
-subprojects {
-    repositories {
-        maven { url '${repo.uri}' }
-    }
-    configurations {
-        compile
-    }
-}
-project(':a') {
-    dependencies {
-        compile 'org.gradle.test:external1:1.0:classifier1'
-    }
-    task test(dependsOn: configurations.compile) << {
-        assert configurations.compile.collect { it.name } == ['external1-1.0-classifier1.jar']
-        assert configurations.compile.resolvedConfiguration.resolvedArtifacts.collect { "\${it.name}-\${it.classifier}" } == ['external1-classifier1']
-    }
-}
-project(':b') {
-    dependencies {
-        compile 'org.gradle.test:external1:1.0:classifier2'
-    }
-    task test(dependsOn: configurations.compile) << {
-        assert configurations.compile.collect { it.name } == ['external1-1.0-classifier2.jar']
-        assert configurations.compile.resolvedConfiguration.resolvedArtifacts.collect { "\${it.name}-\${it.classifier}" } == ['external1-classifier2']
-    }
-}
-"""
-
-        inTestDirectory().withTasks('a:test').run()
-        inTestDirectory().withTasks('b:test').run()
-    }
-
-    @Test
-    @Issue("GRADLE-739")
-    public void singleConfigurationCanContainMultipleArtifactsThatOnlyDifferByClassifier() {
-        def module = repo.module('org.gradle.test', 'external1', '1.0')
-        module.artifact(classifier: 'baseClassifier')
-        module.artifact(classifier: 'extendedClassifier')
-        module.publish()
-        repo.module('org.gradle.test', 'other', '1.0').publish()
-
-        testFile('build.gradle') << """
-repositories {
-    maven { url '${repo.uri}' }
-}
-configurations {
-    base
-    extendedWithClassifier.extendsFrom base
-    extendedWithOther.extendsFrom base
-    justDefault
-    justClassifier
-    rawBase
-    rawExtended.extendsFrom rawBase
-    cBase
-    cExtended.extendsFrom cBase
-}
-dependencies {
-    base 'org.gradle.test:external1:1.0'
-    base 'org.gradle.test:external1:1.0:baseClassifier'
-    extendedWithClassifier 'org.gradle.test:external1:1.0:extendedClassifier'
-    extendedWithOther 'org.gradle.test:other:1.0'
-    justDefault 'org.gradle.test:external1:1.0'
-    justClassifier 'org.gradle.test:external1:1.0:baseClassifier'
-    justClassifier 'org.gradle.test:external1:1.0:extendedClassifier'
-    rawBase 'org.gradle.test:external1:1.0'
-    rawExtended 'org.gradle.test:external1:1.0:extendedClassifier'
-}
-
-def checkDeps(config, expectedDependencies) {
-    assert config.collect({ it.name }) as Set == expectedDependencies as Set
-}
-
-task test << {
-    checkDeps configurations.base, ['external1-1.0.jar', 'external1-1.0-baseClassifier.jar']
-    checkDeps configurations.extendedWithOther, ['external1-1.0.jar', 'external1-1.0-baseClassifier.jar', 'other-1.0.jar']
-    checkDeps configurations.extendedWithClassifier, ['external1-1.0.jar', 'external1-1.0-baseClassifier.jar', 'external1-1.0-extendedClassifier.jar']
-    checkDeps configurations.justDefault, ['external1-1.0.jar']
-    checkDeps configurations.justClassifier, ['external1-1.0-baseClassifier.jar', 'external1-1.0-extendedClassifier.jar']
-    checkDeps configurations.rawBase, ['external1-1.0.jar']
-    checkDeps configurations.rawExtended, ['external1-1.0.jar', 'external1-1.0-extendedClassifier.jar']
-}
-"""
-        inTestDirectory().withTasks('test').run()
-    }
-
-    @Test
-    @Issue("GRADLE-739")
-    public void canUseClassifiersCombinedWithArtifactWithNonStandardPackaging() {
-        def module = repo.module('org.gradle.test', 'external1', '1.0')
-        module.artifact(type: 'txt')
-        module.artifact(classifier: 'baseClassifier', type: 'jar')
-        module.artifact(classifier: 'extendedClassifier', type: 'jar')
-        module.hasType('zip')
-        module.publish()
-        repo.module('org.gradle.test', 'other', '1.0').publish()
-
-        testFile('build.gradle') << """
-repositories {
-    maven { url '${repo.uri}' }
-}
-configurations {
-    base
-    extended.extendsFrom base
-    extendedWithClassifier.extendsFrom base
-    extendedWithType.extendsFrom base
-}
-dependencies {
-    base 'org.gradle.test:external1:1.0'
-    base 'org.gradle.test:external1:1.0:baseClassifier'
-    extended 'org.gradle.test:other:1.0'
-    extendedWithClassifier 'org.gradle.test:external1:1.0:extendedClassifier'
-    extendedWithType 'org.gradle.test:external1:1.0 at txt'
-}
-
-def checkDeps(config, expectedDependencies) {
-    assert config.collect({ it.name }) as Set == expectedDependencies as Set
-}
-
-task test << {
-    checkDeps configurations.base, ['external1-1.0.zip', 'external1-1.0-baseClassifier.jar']
-    checkDeps configurations.extended, ['external1-1.0.zip', 'external1-1.0-baseClassifier.jar', 'other-1.0.jar']
-    checkDeps configurations.extendedWithClassifier, ['external1-1.0.zip', 'external1-1.0-baseClassifier.jar', 'external1-1.0-extendedClassifier.jar']
-    checkDeps configurations.extendedWithType, ['external1-1.0.zip', 'external1-1.0-baseClassifier.jar', 'external1-1.0.txt']
-}
-"""
-        executer.withDeprecationChecksDisabled()
-        def result = inTestDirectory().withTasks('test').run()
-        assert result.output.contains('Relying on packaging to define the extension of the main artifact has been deprecated')
-    }
-
-    @Test
-    @Issue("GRADLE-739")
-    public void configurationCanContainMultipleArtifactsThatOnlyDifferByType() {
-        def module = repo.module('org.gradle.test', 'external1', '1.0')
-        module.artifact(type: 'zip')
-        module.artifact(classifier: 'classifier')
-        module.artifact(classifier: 'classifier', type: 'bin')
-        module.publish()
-
-        testFile('build.gradle') << """
-repositories {
-    maven { url '${repo.uri}' }
-}
-configurations {
-    base
-    extended.extendsFrom base
-    extended2.extendsFrom base
-}
-dependencies {
-    base 'org.gradle.test:external1:1.0'
-    base 'org.gradle.test:external1:1.0 at zip'
-    extended 'org.gradle.test:external1:1.0:classifier'
-    extended2 'org.gradle.test:external1:1.0:classifier at bin'
-}
-
-def checkDeps(config, expectedDependencies) {
-    assert config.collect({ it.name }) as Set == expectedDependencies as Set
-}
-
-task test << {
-    checkDeps configurations.base, ['external1-1.0.jar', 'external1-1.0.zip']
-    checkDeps configurations.extended, ['external1-1.0.jar', 'external1-1.0.zip', 'external1-1.0-classifier.jar']
-    checkDeps configurations.extended2, ['external1-1.0.jar', 'external1-1.0.zip', 'external1-1.0-classifier.bin']
-}
-"""
-        inTestDirectory().withTasks('test').run()
-    }
-
-    @Test
-    public void "dependencies that are excluded by a dependency are not retrieved"() {
-        repo.module('org.gradle.test', 'one', '1.0').publish()
-        repo.module('org.gradle.test', 'two', '1.0').publish()
-        def module = repo.module('org.gradle.test', 'external1', '1.0')
-        module.dependsOn('org.gradle.test', 'one', '1.0')
-        module.artifact(classifier: 'classifier')
-        module.publish()
-
-        testFile('build.gradle') << """
-repositories {
-    maven { url '${repo.uri}' }
-}
-configurations {
-    reference
-    excluded
-    extendedExcluded.extendsFrom excluded
-    excludedWithClassifier
-}
-dependencies {
-    reference 'org.gradle.test:external1:1.0'
-    excluded 'org.gradle.test:external1:1.0', { exclude module: 'one' }
-    extendedExcluded 'org.gradle.test:two:1.0'
-    excludedWithClassifier 'org.gradle.test:external1:1.0', { exclude module: 'one' }
-    excludedWithClassifier 'org.gradle.test:external1:1.0:classifier', { exclude module: 'one' }
-}
-
-def checkDeps(config, expectedDependencies) {
-    assert config*.name as Set == expectedDependencies as Set
-}
-
-task test << {
-    checkDeps configurations.reference, ['external1-1.0.jar', 'one-1.0.jar']
-    checkDeps configurations.excluded, ['external1-1.0.jar']
-    checkDeps configurations.extendedExcluded, ['external1-1.0.jar', 'two-1.0.jar']
-    checkDeps configurations.excludedWithClassifier, ['external1-1.0.jar', 'external1-1.0-classifier.jar']
-}
-"""
-        inTestDirectory().withTasks('test').run()
-    }
-
-    @Test
-    public void "dependencies that are globally excluded are not retrieved"() {
-        repo.module('org.gradle.test', 'direct', '1.0').publish()
-        repo.module('org.gradle.test', 'transitive', '1.0').publish()
-        def module = repo.module('org.gradle.test', 'external', '1.0')
-        module.dependsOn('org.gradle.test', 'transitive', '1.0')
-        module.publish()
-
-        testFile('build.gradle') << """
-repositories {
-    maven { url '${repo.uri}' }
-}
-configurations {
-    excluded {
-        exclude module: 'direct'
-        exclude module: 'transitive'
-    }
-    extendedExcluded.extendsFrom excluded
-}
-dependencies {
-    excluded 'org.gradle.test:external:1.0'
-    excluded 'org.gradle.test:direct:1.0'
-}
-
-def checkDeps(config, expectedDependencies) {
-    assert config*.name as Set == expectedDependencies as Set
-}
-
-task test << {
-    checkDeps configurations.excluded, ['external-1.0.jar']
-    checkDeps configurations.extendedExcluded, ['external-1.0.jar']
-}
-"""
-        inTestDirectory().withTasks('test').run()
-    }
-
-    @Test
-    public void "does not attempt to resolve an excluded dependency"() {
-        def module = repo.module('org.gradle.test', 'external', '1.0')
-        module.dependsOn('org.gradle.test', 'unknown1', '1.0')
-        module.dependsOn('org.gradle.test', 'unknown2', '1.0')
-        module.publish()
-
-        testFile('build.gradle') << """
-repositories {
-    maven { url '${repo.uri}' }
-}
-configurations {
-    excluded {
-        exclude module: 'unknown2'
-    }
-}
-dependencies {
-    excluded 'org.gradle.test:external:1.0', { exclude module: 'unknown1' }
-    excluded 'org.gradle.test:unknown2:1.0'
-}
-
-def checkDeps(config, expectedDependencies) {
-    assert config*.name as Set == expectedDependencies as Set
-}
-
-task test << {
-    checkDeps configurations.excluded, ['external-1.0.jar']
-}
-"""
-        inTestDirectory().withTasks('test').run()
-    }
-
-    @Test
-    public void nonTransitiveDependenciesAreNotRetrieved() {
-        repo.module('org.gradle.test', 'one', '1.0').publish()
-        repo.module('org.gradle.test', 'two', '1.0').publish()
-        def module = repo.module('org.gradle.test', 'external1', '1.0')
-        module.dependsOn('org.gradle.test', 'one', '1.0')
-        module.artifact(classifier: 'classifier')
-        module.publish()
-
-        testFile('build.gradle') << """
-repositories {
-    maven { url '${repo.uri}' }
-}
-configurations {
-    transitive
-    nonTransitive
-    extendedNonTransitive.extendsFrom nonTransitive
-    extendedBoth.extendsFrom transitive, nonTransitive
-    mergedNonTransitive
-}
-dependencies {
-    transitive 'org.gradle.test:external1:1.0'
-    nonTransitive 'org.gradle.test:external1:1.0', { transitive = false }
-    extendedNonTransitive 'org.gradle.test:two:1.0'
-    mergedNonTransitive 'org.gradle.test:external1:1.0', {transitive = false }
-    mergedNonTransitive 'org.gradle.test:external1:1.0:classifier', { transitive = false }
-}
-
-def checkDeps(config, expectedDependencies) {
-    assert config.collect({ it.name }) as Set == expectedDependencies as Set
-}
-
-task test << {
-    checkDeps configurations.transitive, ['external1-1.0.jar', 'one-1.0.jar']
-    checkDeps configurations.nonTransitive, ['external1-1.0.jar']
-    checkDeps configurations.extendedNonTransitive, ['external1-1.0.jar', 'two-1.0.jar']
-    checkDeps configurations.extendedBoth, ['external1-1.0.jar', 'one-1.0.jar']
-    checkDeps configurations.mergedNonTransitive, ['external1-1.0.jar', 'external1-1.0-classifier.jar']
-}
-"""
-        inTestDirectory().withTasks('test').run()
-    }
-
-    @Test
-    public void "configuration transitive = false overrides dependency transitive flag"() {
-        repo.module('org.gradle.test', 'one', '1.0').publish()
-        def module = repo.module('org.gradle.test', 'external1', '1.0')
-        module.dependsOn('org.gradle.test', 'one', '1.0')
-        module.publish()
-
-        testFile('build.gradle') << """
-repositories {
-    maven { url '${repo.uri}' }
-}
-configurations {
-    override { transitive = false }
-}
-dependencies {
-    override 'org.gradle.test:external1:1.0'
-}
-
-task test << {
-    assert configurations.override.collect { it.name } == ['external1-1.0.jar']
-}
-"""
-
-        inTestDirectory().withTasks('test').run()
-    }
-
-    /*
-     * Originally, we were aliasing dependency descriptors that were identical. This caused alias errors when we subsequently modified one of these descriptors.
-     */
-
-    @Test
-    public void addingClassifierToDuplicateDependencyDoesNotAffectOriginal() {
-        def module = repo.module('org.gradle.test', 'external1', '1.0')
-        module.artifact(classifier: 'withClassifier')
-        module.publish()
-
-        testFile('build.gradle') << """
-repositories {
-    maven { url '${repo.uri}' }
-}
-configurations {
-    a
-    b
-}
-dependencies {
-    a 'org.gradle.test:external1:1.0'
-    b 'org.gradle.test:external1:1.0', 'org.gradle.test:external1:1.0:withClassifier'
-}
-
-def checkDeps(config, expectedDependencies) {
-    assert config.collect({ it.name }) as Set == expectedDependencies as Set
-}
-
-task test << {
-    checkDeps configurations.a, ['external1-1.0.jar']
-    checkDeps configurations.b, ['external1-1.0-withClassifier.jar', 'external1-1.0.jar']
-}
-"""
-        inTestDirectory().withTasks('test').run()
-    }
-
-    @Test
-    public void reportsUnknownDependencyError() {
-        File buildFile = testFile("projectWithUnknownDependency.gradle");
-        ExecutionFailure failure = usingBuildFile(buildFile).runWithFailure();
-        failure.assertHasFileName("Build file '" + buildFile.getPath() + "'");
-        failure.assertHasDescription("Execution failed for task ':listJars'");
-        failure.assertThatCause(startsWith("Could not resolve all dependencies for configuration ':compile'"));
-        failure.assertThatCause(containsString("Could not find group:test, module:unknownProjectA, version:1.2."));
-        failure.assertThatCause(containsString("Could not find group:test, module:unknownProjectB, version:2.1.5."));
-    }
-
-    @Test
-    public void projectCanDependOnItself() {
-        TestFile buildFile = testFile("build.gradle");
-        buildFile << '''
-            configurations { compile; add('default') }
-            dependencies { compile project(':') }
-            task jar1(type: Jar) { destinationDir = buildDir; baseName = '1' }
-            task jar2(type: Jar) { destinationDir = buildDir; baseName = '2' }
-            artifacts { compile jar1; 'default' jar2 }
-            task listJars << {
-                assert configurations.compile.collect { it.name } == ['2.jar']
-            }
-'''
-
-        inTestDirectory().withTasks("listJars").run()
-    }
-
-    @Test
-    public void canSpecifyProducerTasksForFileDependency() {
-        testFile("settings.gradle").write("include 'sub'");
-        testFile("build.gradle") << '''
-            configurations { compile }
-            dependencies { compile project(path: ':sub', configuration: 'compile') }
-            task test(dependsOn: configurations.compile) << {
-                assert file('sub/sub.jar').isFile()
-            }
-'''
-        testFile("sub/build.gradle") << '''
-            configurations { compile }
-            dependencies { compile files('sub.jar') { builtBy 'jar' } }
-            task jar << { file('sub.jar').text = 'content' }
-'''
-
-        inTestDirectory().withTasks("test").run().assertTasksExecuted(":sub:jar", ":test");
-    }
-
-    def getRepo() {
-        return maven(testFile('repo'))
-    }
-}
-
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy
deleted file mode 100644
index ecd40c8..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.MavenModule
-import org.gradle.integtests.fixtures.TestResources
-import org.gradle.test.fixtures.server.http.HttpServer
-import org.junit.Rule
-
-class ArtifactOnlyResolutionIntegrationTest extends AbstractIntegrationSpec {
-    @Rule public final HttpServer server = new HttpServer()
-    @Rule public final TestResources resources = new TestResources();
-
-    MavenModule projectA
-
-    def "setup"() {
-        requireOwnUserHomeDir()
-
-        projectA = mavenRepo.module('group', 'projectA').publish()
-        server.start()
-
-        buildFile << """
-repositories {
-    maven { url 'http://localhost:${server.port}/repo1' }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.0 at jar'
-}
-
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-    }
-
-    def "can resolve and cache artifact-only dependencies from a HTTP repository"() {
-        when:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.0.jar')
-
-        and:
-        newRequestForModuleDoesNotContactServer()
-    }
-
-    def "can resolve and cache artifact-only dependencies from a HTTP repository with no descriptor"() {
-        when:
-        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0.pom')
-        server.expectHead('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.0.jar')
-
-        and:
-        newRequestForModuleDoesNotContactServer()
-    }
-
-    private newRequestForModuleDoesNotContactServer() {
-        def snapshot = file('libs/projectA-1.0.jar').snapshot()
-        server.resetExpectations()
-        def result = run 'retrieve'
-        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
-        return result
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy
deleted file mode 100644
index 8e31aab..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve;
-
-
-public class CacheResolveIntegrationTest extends AbstractDependencyResolutionTest {
-
-    public void "cache handles manual deletion of cached artifacts"() {
-        server.start()
-
-        given:
-        def module = ivyHttpRepo.module('group', 'projectA', '1.2').publish()
-
-        def cacheDir = distribution.userHomeDir.file('caches').toURI()
-
-        and:
-        buildFile << """
-repositories {
-    ivy { url "${ivyHttpRepo.uri}" }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.2' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-task deleteCacheFiles(type: Delete) {
-    delete fileTree(dir: '${cacheDir}', includes: ['**/projectA/**'])
-}
-"""
-
-        and:
-        module.allowAll()
-
-        and:
-        succeeds('listJars')
-        succeeds('deleteCacheFiles')
-        
-        when:
-        server.resetExpectations()
-        module.expectIvyGet()
-        module.expectJarGet()
-
-        then:
-        succeeds('listJars')
-    }
-
-    public void "cache entries are segregated between different repositories"() {
-        server.start()
-        given:
-        def repo1 = ivyHttpRepo('ivy-repo-a')
-        def module1 = repo1.module('org.gradle', 'testproject', '1.0').publish()
-        def repo2 = ivyHttpRepo('ivy-repo-b')
-        def module2 = repo2.module('org.gradle', 'testproject', '1.0').publishWithChangedContent()
-
-        and:
-        settingsFile << "include 'a','b'"
-        buildFile << """
-subprojects {
-    configurations {
-        test
-    }
-    dependencies {
-        test "org.gradle:testproject:1.0"
-    }
-    task retrieve(type: Sync) {
-        into 'build'
-        from configurations.test
-    }
-}
-project('a') {
-    repositories {
-        ivy { url "${repo1.uri}" }
-    }
-}
-project('b') {
-    repositories {
-        ivy { url "${repo2.uri}" }
-    }
-    retrieve.dependsOn(':a:retrieve')
-}
-"""
-
-        when:
-        module1.expectIvyGet()
-        module1.expectJarGet()
-
-        module2.expectIvyHead()
-        module2.expectIvySha1Get()
-        module2.expectIvyGet()
-        module2.expectJarHead()
-        module2.expectJarSha1Get()
-        module2.expectJarGet()
-
-        then:
-        succeeds 'retrieve'
-
-        and:
-        file('a/build/testproject-1.0.jar').assertIsCopyOf(module1.jarFile)
-        file('b/build/testproject-1.0.jar').assertIsCopyOf(module2.jarFile)
-    }
-
-    public void "reuses a cached artifact retrieved from a different repository when sha1 matches"() {
-        server.start()
-        given:
-        def repo1 = ivyHttpRepo('ivy-repo-a')
-        def module1 = repo1.module('org.gradle', 'testproject', '1.0').publish()
-        def repo2 = ivyHttpRepo('ivy-repo-b')
-        def module2 = repo2.module('org.gradle', 'testproject', '1.0').publish()
-
-        and:
-        settingsFile << "include 'a','b'"
-        buildFile << """
-subprojects {
-    configurations {
-        test
-    }
-    dependencies {
-        test "org.gradle:testproject:1.0"
-    }
-    task retrieve(type: Sync) {
-        into 'build'
-        from configurations.test
-    }
-}
-project('a') {
-    repositories {
-        ivy { url "${repo1.uri}" }
-    }
-}
-project('b') {
-    repositories {
-        ivy { url "${repo2.uri}" }
-    }
-    retrieve.dependsOn(':a:retrieve')
-}
-"""
-
-        when:
-        module1.expectIvyGet()
-        module1.expectJarGet()
-
-        module2.expectIvyHead()
-        module2.expectIvySha1Get()
-        module2.expectJarHead()
-        module2.expectJarSha1Get()
-
-        then:
-        succeeds 'retrieve'
-
-        and:
-        file('a/build/testproject-1.0.jar').assertIsCopyOf(module1.jarFile)
-        file('b/build/testproject-1.0.jar').assertIsCopyOf(module2.jarFile)
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ClientModuleDependenciesResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ClientModuleDependenciesResolveIntegrationTest.groovy
deleted file mode 100644
index d128a30..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ClientModuleDependenciesResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve;
-
-
-import org.junit.Test
-
-/**
- * @author Hans Dockter
- */
-public class ClientModuleDependenciesResolveIntegrationTest extends AbstractDependencyResolutionTest {
-    @Test
-    public void testResolve() {
-        when:
-        // the actual testing is done in the build script.
-        File projectDir = new File(distribution.getSamplesDir(), "clientModuleDependencies/shared");
-        then:
-        executer.inDirectory(projectDir).withTasks("testDeps").run();
-
-        when:
-        projectDir = new File(distribution.getSamplesDir(), "clientModuleDependencies/api");
-        then:
-        executer.inDirectory(projectDir).withTasks("testDeps").run();
-    }
-
-    @Test
-    public void "uses metadata from Client Module and looks up artifact in declared repositories"() {
-        distribution.requireOwnUserHomeDir()
-        given:
-        def repo = ivyRepo()
-        def projectA = repo.module('group', 'projectA', '1.2')
-        def projectB = repo.module('group', 'projectB', '1.3')
-        projectA.publish()
-        projectB.publish()
-
-        server.start()
-
-        and:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
-    ivy { url "http://localhost:${server.port}/repo2" }
-}
-configurations { compile }
-dependencies {
-    compile module("group:projectA:1.2") {
-       dependency("group:projectB:1.3")
-    }
-}
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar', 'projectB-1.3.jar']
-}
-"""
-        server.expectGet('/repo/group/projectB/1.3/ivy-1.3.xml', projectB.ivyFile)
-        server.expectGetMissing('/repo/group/projectA/1.2/ivy-1.2.xml')
-        server.expectHeadMissing('/repo/group/projectA/1.2/projectA-1.2.jar')
-        server.expectGet('/repo2/group/projectA/1.2/ivy-1.2.xml', projectA.ivyFile)
-        server.expectGet('/repo2/group/projectA/1.2/projectA-1.2.jar', projectA.jarFile)
-        server.expectGet('/repo/group/projectB/1.3/projectB-1.3.jar', projectB.jarFile)
-
-        expect:
-        succeeds('listJars')
-
-//        given:
-        server.resetExpectations()
-
-//        expect:
-        succeeds('listJars')
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/DependenciesResolveIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/DependenciesResolveIntegrationTest.java
deleted file mode 100644
index 37374f0..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/DependenciesResolveIntegrationTest.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve;
-
-import org.gradle.integtests.fixtures.GradleDistribution;
-import org.gradle.integtests.fixtures.GradleDistributionExecuter;
-import org.gradle.integtests.fixtures.Sample;
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public class DependenciesResolveIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution();
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter();
-    @Rule public final Sample sample = new Sample("dependencies");
-
-    @Test
-    public void testResolve() {
-        dist.requireOwnUserHomeDir();
-
-        // the actual testing is done in the build script.
-        File projectDir = sample.getDir();
-        executer.inDirectory(projectDir).withTasks("test").run();
-    }   
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/DependencyNotationIntegrationSpec.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/DependencyNotationIntegrationSpec.groovy
deleted file mode 100644
index 055f959..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/DependencyNotationIntegrationSpec.groovy
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.resolve
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.hamcrest.Matchers
-
-/**
- * by Szczepan Faber, created at: 11/9/11
- */
-class DependencyNotationIntegrationSpec extends AbstractIntegrationSpec {
-
-    def "understands dependency notations"() {
-        when:
-        buildFile <<  """
-import org.gradle.api.internal.artifacts.dependencies.*
-configurations {
-    conf
-    gradleStuff
-    allowsCollections
-}
-
-def someDependency = new DefaultSelfResolvingDependency(files('foo.txt'))
-dependencies {
-    conf someDependency
-    conf "org.mockito:mockito-core:1.8"
-    conf group: 'org.spockframework', name: 'spock-core', version: '1.0'
-    conf module('org.foo:moduleOne:1.0'), module('org.foo:moduleTwo:1.0')
-
-    gradleStuff gradleApi()
-
-    allowsCollections "org.mockito:mockito-core:1.8", someDependency
-}
-
-task checkDeps << {
-    def deps = configurations.conf.incoming.dependencies
-    assert deps.contains(someDependency)
-    assert deps.find { it instanceof ExternalDependency && it.group == 'org.mockito' && it.name == 'mockito-core' && it.version == '1.8'  }
-    assert deps.find { it instanceof ExternalDependency && it.group == 'org.spockframework' && it.name == 'spock-core' && it.version == '1.0'  }
-    assert deps.find { it instanceof ClientModule && it.name == 'moduleOne' && it.group == 'org.foo' }
-    assert deps.find { it instanceof ClientModule && it.name == 'moduleTwo' && it.version == '1.0' }
-
-    deps = configurations.gradleStuff.dependencies
-    assert deps.findAll { it instanceof SelfResolvingDependency }.size() > 0 : "should include gradle api jars"
-
-    deps = configurations.allowsCollections.dependencies
-    assert deps.size() == 2
-    assert deps.find { it instanceof ExternalDependency && it.group == 'org.mockito' }
-    assert deps.contains(someDependency)
-}
-"""
-        then:
-        succeeds 'checkDeps'
-    }
-
-    def "understands project notations"() {
-        when:
-        settingsFile << "include 'otherProject'"
-
-        buildFile <<  """
-configurations {
-    conf
-    confTwo
-}
-
-project(':otherProject') {
-    configurations {
-        otherConf
-    }
-}
-
-dependencies {
-    conf project(':otherProject')
-    confTwo project(path: ':otherProject', configuration: 'otherConf')
-}
-
-task checkDeps << {
-    def deps = configurations.conf.incoming.dependencies
-    assert deps.size() == 1
-    assert deps.find { it.dependencyProject.path == ':otherProject' }
-
-    deps = configurations.confTwo.incoming.dependencies
-    assert deps.size() == 1
-    assert deps.find { it.dependencyProject.path == ':otherProject' && it.projectConfiguration.name == 'otherConf' }
-}
-"""
-        then:
-        succeeds 'checkDeps'
-    }
-
-    def "understands client module notation with dependencies"() {
-        when:
-        buildFile <<  """
-configurations {
-    conf
-}
-
-dependencies {
-    conf module('org.foo:moduleOne:1.0') {
-        dependency 'org.foo:bar:1.0'
-        dependencies ('org.foo:one:1', 'org.foo:two:1')
-        dependency ('high:five:5') { transitive = false }
-    }
-}
-
-task checkDeps << {
-    def deps = configurations.conf.incoming.dependencies
-    assert deps.size() == 1
-    def dep = deps.find { it instanceof ClientModule && it.name == 'moduleOne' }
-    assert dep
-    assert dep.dependencies.size() == 4
-    assert dep.dependencies.find { it.group == 'org.foo' && it.name == 'bar' && it.version == '1.0' && it.transitive == true }
-    assert dep.dependencies.find { it.group == 'org.foo' && it.name == 'one' && it.version == '1' }
-    assert dep.dependencies.find { it.group == 'org.foo' && it.name == 'two' && it.version == '1' }
-    assert dep.dependencies.find { it.group == 'high' && it.name == 'five' && it.version == '5' && it.transitive == false }
-}
-"""
-        then:
-        succeeds 'checkDeps'
-    }
-
-    def "fails gracefully for invalid notations"() {
-        when:
-        buildFile <<  """
-configurations {
-    conf
-}
-
-dependencies {
-    conf 100
-}
-
-task checkDeps
-"""
-        then:
-        fails 'checkDeps'
-        failure.assertThatCause(Matchers.startsWith("Cannot convert the provided notation to an object of type Dependency: 100."))
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy
deleted file mode 100644
index 7877a8a..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-
-class ProjectDependencyResolveIntegrationTest extends AbstractIntegrationSpec {
-    public void "project dependency includes artifacts and transitive dependencies of default configuration in target project"() {
-        given:
-        mavenRepo.module("org.other", "externalA", 1.2).publish()
-        mavenRepo.module("org.other", "externalB", 2.1).publish()
-
-        and:
-        file('settings.gradle') << "include 'a', 'b'"
-
-        and:
-        buildFile << """
-allprojects {
-    repositories { maven { url '$mavenRepo.uri' } }
-}
-project(":a") {
-    configurations {
-        api
-        'default' { extendsFrom api }
-    }
-    dependencies {
-        api "org.other:externalA:1.2"
-        'default' "org.other:externalB:2.1"
-    }
-    task jar(type: Jar) { baseName = 'a' }
-    artifacts { api jar }
-}
-project(":b") {
-    configurations {
-        compile
-    }
-    dependencies {
-        compile project(':a')
-    }
-    task check(dependsOn: configurations.compile) << {
-        assert configurations.compile.collect { it.name } == ['a.jar', 'externalA-1.2.jar', 'externalB-2.1.jar']
-    }
-}
-"""
-
-        expect:
-        succeeds "check"
-    }
-
-    public void "project dependency that specifies a target configuration includes artifacts and transitive dependencies of selected configuration"() {
-        given:
-        mavenRepo.module("org.other", "externalA", 1.2).publish()
-
-        and:
-        file('settings.gradle') << "include 'a', 'b'"
-
-        and:
-        buildFile << """
-allprojects {
-    repositories { maven { url '$mavenRepo.uri' } }
-}
-project(":a") {
-    configurations {
-        api
-        runtime { extendsFrom api }
-    }
-    dependencies {
-        api "org.other:externalA:1.2"
-    }
-    task jar(type: Jar) { baseName = 'a' }
-    artifacts { api jar }
-}
-project(":b") {
-    configurations {
-        compile
-    }
-    dependencies {
-        compile project(path: ':a', configuration: 'runtime')
-    }
-    task check(dependsOn: configurations.compile) << {
-        assert configurations.compile.collect { it.name } == ['a.jar', 'externalA-1.2.jar']
-    }
-}
-"""
-
-        expect:
-        succeeds "check"
-    }
-
-    public void "resolved project artifacts contain project version in their names"() {
-        given:
-        file('settings.gradle') << "include 'a', 'b'"
-
-        and:
-        file('a/build.gradle') << '''
-            apply plugin: 'base'
-            configurations { compile }
-            task aJar(type: Jar) { }
-            artifacts { compile aJar }
-'''
-        file('b/build.gradle') << '''
-            apply plugin: 'base'
-            version = 'early'
-            configurations { compile }
-            task bJar(type: Jar) { }
-            gradle.taskGraph.whenReady { project.version = 'late' }
-            artifacts { compile bJar }
-'''
-        file('build.gradle') << '''
-            configurations { compile }
-            dependencies { compile project(path: ':a', configuration: 'compile'), project(path: ':b', configuration: 'compile') }
-            task test(dependsOn: configurations.compile) << {
-                assert configurations.compile.collect { it.name } == ['a.jar', 'b-late.jar']
-            }
-'''
-
-        expect:
-        succeeds "test"
-    }
-
-    public void "project dependency that references an artifact includes the matching artifact only plus the transitive dependencies of referenced configuration"() {
-        given:
-        mavenRepo.module("group", "externalA", 1.5).publish()
-
-        and:
-        file('settings.gradle') << "include 'a', 'b'"
-
-        and:
-        buildFile << """
-allprojects {
-    apply plugin: 'base'
-    repositories { maven { url '${mavenRepo.uri}' } }
-}
-
-project(":a") {
-    configurations { 'default' {} }
-    dependencies { 'default' 'group:externalA:1.5' }
-    task aJar(type: Jar) { }
-    artifacts { 'default' aJar }
-}
-
-project(":b") {
-    configurations { compile }
-    dependencies { compile(project(':a')) { artifact { name = 'a'; type = 'jar' } } }
-    task test {
-        inputs.files configurations.compile
-        doFirst {
-            assert configurations.compile.files.collect { it.name } == ['a.jar', 'externalA-1.5.jar']
-        }
-    }
-}
-"""
-
-        expect:
-        succeeds 'test'
-    }
-
-    public void "non-transitive project dependency includes only the artifacts of the target configuration"() {
-        given:
-        mavenRepo.module("group", "externalA", 1.5).publish()
-
-        and:
-        file('settings.gradle') << "include 'a', 'b'"
-
-        and:
-        buildFile << '''
-allprojects {
-    apply plugin: 'java'
-    repositories { maven { url rootProject.uri('repo') } }
-}
-project(':a') {
-    dependencies {
-        compile 'group:externalA:1.5'
-        compile files('libs/externalB.jar')
-    }
-}
-project(':b') {
-    dependencies {
-        compile project(':a'), { transitive = false }
-    }
-    task listJars << {
-        assert configurations.compile.collect { it.name } == ['a.jar']
-    }
-}
-'''
-
-        expect:
-        succeeds "listJars"
-    }
-
-    public void "can have cycle in project dependencies"() {
-        given:
-        file('settings.gradle') << "include 'a', 'b', 'c'"
-
-        and:
-        buildFile << """
-
-subprojects {
-    apply plugin: 'base'
-    configurations {
-        'default'
-        other
-    }
-    task jar(type: Jar)
-    artifacts {
-        'default' jar
-    }
-}
-
-project('a') {
-    dependencies {
-        'default' project(':b')
-        other project(':b')
-    }
-    task listJars {
-        dependsOn configurations.default
-        dependsOn configurations.other
-        doFirst {
-            def jars = configurations.default.collect { it.name } as Set
-            assert jars == ['a.jar', 'b.jar', 'c.jar'] as Set
-
-            jars = configurations.other.collect { it.name } as Set
-            assert jars == ['a.jar', 'b.jar', 'c.jar'] as Set
-        }
-    }
-}
-
-project('b') {
-    dependencies {
-        'default' project(':c')
-    }
-}
-
-project('c') {
-    dependencies {
-        'default' project(':a')
-    }
-}
-"""
-
-        expect:
-        succeeds "listJars"
-    }
-
-    // this test is largely covered by other tests, but does ensure that there is nothing special about
-    // project dependencies that are “built” by built in plugins like the Java plugin's created jars
-    def "can use zip files as project dependencies"() {
-        given:
-        file("settings.gradle") << "include 'a'; include 'b'"
-        file("a/some.txt") << "foo"
-        file("a/build.gradle") << """
-            group = "g"
-            version = 1.0
-            
-            apply plugin: 'base'
-            task zip(type: Zip) {
-                from "some.txt"
-            }
-
-            artifacts {
-                delegate.default zip
-            }
-        """
-        file("b/build.gradle") << """
-            configurations { conf }
-            dependencies {
-                conf project(":a")
-            }
-            
-            task copyZip(type: Copy) {
-                from configurations.conf
-                into "\$buildDir/copied"
-            }
-        """
-        
-        when:
-        succeeds ":b:copyZip"
-        
-        then:
-        ":b:copyZip" in  nonSkippedTasks 
-        
-        and:
-        file("b/build/copied/a-1.0.zip").exists()
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/VersionConflictResolutionIntegTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/VersionConflictResolutionIntegTest.groovy
deleted file mode 100644
index 237bc0a..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/VersionConflictResolutionIntegTest.groovy
+++ /dev/null
@@ -1,815 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve
-
-import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.junit.Test
-import spock.lang.Issue
-
-import static org.hamcrest.Matchers.containsString
-
-/**
- * @author Szczepan Faber, @date 03.03.11
- */
-class VersionConflictResolutionIntegTest extends AbstractIntegrationTest {
-
-    @Test
-    void "strict conflict resolution should fail due to conflict"() {
-        repo.module("org", "foo", '1.3.3').publish()
-        repo.module("org", "foo", '1.4.4').publish()
-
-        def settingsFile = file("settings.gradle")
-        settingsFile << "include 'api', 'impl', 'tool'"
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-allprojects {
-	apply plugin: 'java'
-	repositories {
-		maven { url "${repo.uri}" }
-	}
-}
-
-project(':api') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.3.3')
-	}
-}
-
-project(':impl') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.4.4')
-	}
-}
-
-project(':tool') {
-	dependencies {
-		compile project(':api')
-		compile project(':impl')
-	}
-
-	configurations.compile.resolutionStrategy.failOnVersionConflict()
-}
-"""
-
-        //when
-        def result = executer.withTasks("tool:dependencies").runWithFailure()
-
-        //then
-        result.assertThatCause(containsString('A conflict was found between the following modules:'))
-    }
-
-    @Test
-    void "strict conflict resolution should pass when no conflicts"() {
-        repo.module("org", "foo", '1.3.3').publish()
-
-        def settingsFile = file("settings.gradle")
-        settingsFile << "include 'api', 'impl', 'tool'"
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-allprojects {
-	apply plugin: 'java'
-	repositories {
-		maven { url "${repo.uri}" }
-	}
-}
-
-project(':api') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.3.3')
-	}
-}
-
-project(':impl') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.3.3')
-	}
-}
-
-project(':tool') {
-	dependencies {
-		compile project(':api')
-		compile project(':impl')
-	}
-
-	configurations.all { resolutionStrategy.failOnVersionConflict() }
-}
-"""
-
-        //when
-        executer.withTasks("tool:dependencies").run()
-
-        //then no exceptions are thrown
-    }
-
-    @Test
-    void "strict conflict strategy can be used with forced modules"() {
-        repo.module("org", "foo", '1.3.3').publish()
-        repo.module("org", "foo", '1.4.4').publish()
-        repo.module("org", "foo", '1.5.5').publish()
-
-        def settingsFile = file("settings.gradle")
-        settingsFile << "include 'api', 'impl', 'tool'"
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-allprojects {
-	apply plugin: 'java'
-	repositories {
-		maven { url "${repo.uri}" }
-	}
-}
-
-project(':api') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.4.4')
-	}
-}
-
-project(':impl') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.3.3')
-	}
-}
-
-project(':tool') {
-	dependencies {
-		compile project(':api')
-		compile project(':impl')
-		compile('org:foo:1.5.5'){
-		    force = true
-		}
-	}
-
-	configurations.all { resolutionStrategy.failOnVersionConflict() }
-}
-"""
-
-        //when
-        executer.withTasks("tool:dependencies").run()
-
-        //then no exceptions are thrown because we forced a certain version of conflicting dependency
-    }
-
-    @Test
-    void "can force already resolved version of a module and avoid conflict"() {
-        repo.module("org", "foo", '1.3.3').publish()
-        repo.module("org", "foo", '1.4.4').publish()
-
-        def settingsFile = file("settings.gradle")
-        settingsFile << "include 'api', 'impl', 'tool'"
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-allprojects {
-	apply plugin: 'java'
-	repositories {
-		maven { url "${repo.uri}" }
-	}
-}
-
-project(':api') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.4.4')
-	}
-}
-
-project(':impl') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.3.3')
-	}
-}
-
-project(':tool') {
-
-	dependencies {
-		compile project(':api')
-		compile project(':impl')
-	}
-}
-
-allprojects {
-    configurations.all {
-	    resolutionStrategy {
-	        force 'org:foo:1.3.3'
-	        failOnVersionConflict()
-	    }
-	}
-}
-
-"""
-
-        //when
-        executer.withTasks("api:dependencies", "tool:dependencies").run()
-
-        //then no exceptions are thrown because we forced a certain version of conflicting dependency
-    }
-
-    @Test
-    void "can force arbitrary version of a module and avoid conflict"() {
-        repo.module("org", "foo", '1.3.3').publish()
-        repo.module("org", "foobar", '1.3.3').publish()
-        repo.module("org", "foo", '1.4.4').publish()
-        repo.module("org", "foo", '1.5.5').publish()
-
-        def settingsFile = file("settings.gradle")
-        settingsFile << "include 'api', 'impl', 'tool'"
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-allprojects {
-	apply plugin: 'java'
-	repositories {
-		maven { url "${repo.uri}" }
-	}
-	group = 'org.foo.unittests'
-	version = '1.0'
-}
-
-project(':api') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.4.4')
-	}
-}
-
-project(':impl') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.3.3')
-	}
-}
-
-project(':tool') {
-	dependencies {
-		compile project(':api')
-		compile project(':impl')
-	}
-    task checkDeps(dependsOn: configurations.compile) << {
-        assert configurations.compile*.name == ['api-1.0.jar', 'impl-1.0.jar', 'foo-1.5.5.jar']
-        def metaData = configurations.compile.resolvedConfiguration
-        def api = metaData.firstLevelModuleDependencies.find { it.moduleName == 'api' }
-        assert api.children.size() == 1
-        assert api.children.find { it.moduleName == 'foo' && it.moduleVersion == '1.5.5' }
-        def impl = metaData.firstLevelModuleDependencies.find { it.moduleName == 'impl' }
-        assert impl.children.size() == 1
-        assert impl.children.find { it.moduleName == 'foo' && it.moduleVersion == '1.5.5' }
-    }
-}
-
-allprojects {
-    configurations.all {
-        resolutionStrategy {
-            failOnVersionConflict()
-            force 'org:foo:1.5.5'
-        }
-    }
-}
-
-"""
-
-        // expect
-        executer.withTasks(":tool:checkDeps").run()
-    }
-
-    @Test
-    void "resolves to the latest version by default"() {
-        repo.module("org", "foo", '1.3.3').publish()
-        repo.module("org", "foo", '1.4.4').publish()
-
-        def settingsFile = file("settings.gradle")
-        settingsFile << "include 'api', 'impl', 'tool'"
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-allprojects {
-	apply plugin: 'java'
-	repositories {
-		maven { url "${repo.uri}" }
-	}
-}
-
-project(':api') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.3.3')
-	}
-}
-
-project(':impl') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.4.4')
-	}
-}
-
-project(':tool') {
-	dependencies {
-		compile project(':api')
-		compile project(':impl')
-	}
-    task checkDeps(dependsOn: configurations.compile) << {
-        assert configurations.compile*.name == ['api.jar', 'impl.jar', 'foo-1.4.4.jar']
-    }
-}
-"""
-
-        //expect
-        executer.withTasks("tool:checkDeps").run()
-    }
-
-    @Test
-    void "latest strategy respects forced modules"() {
-        repo.module("org", "foo", '1.3.3').publish()
-        repo.module("org", "foo", '1.4.4').publish()
-
-        def settingsFile = file("settings.gradle")
-        settingsFile << "include 'api', 'impl', 'tool'"
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-allprojects {
-	apply plugin: 'java'
-	repositories {
-		maven { url "${repo.uri}" }
-	}
-}
-
-project(':api') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.3.3')
-	}
-}
-
-project(':impl') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.4.4')
-	}
-}
-
-project(':tool') {
-	dependencies {
-		compile project(':api')
-		compile project(':impl')
-	}
-	configurations.all {
-	    resolutionStrategy {
-	        failOnVersionConflict()
-	        force 'org:foo:1.3.3'
-	    }
-	}
-    task checkDeps(dependsOn: configurations.compile) << {
-        assert configurations.compile*.name == ['api.jar', 'impl.jar', 'foo-1.3.3.jar']
-    }
-}
-"""
-
-        //expect
-        executer.withTasks("tool:checkDeps").run()
-    }
-
-    @Test
-    void "can force the version of a particular module"() {
-        repo.module("org", "foo", '1.3.3').publish()
-        repo.module("org", "foo", '1.4.4').publish()
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-apply plugin: 'java'
-repositories {
-    maven { url "${repo.uri}" }
-}
-
-dependencies {
-    compile 'org:foo:1.3.3'
-}
-
-configurations.all {
-    resolutionStrategy.force 'org:foo:1.4.4'
-}
-
-task checkDeps << {
-    assert configurations.compile*.name == ['foo-1.4.4.jar']
-}
-"""
-
-        //expect
-        executer.withTasks("checkDeps").run()
-    }
-
-    @Test
-    void "can force the version of a direct dependency"() {
-        repo.module("org", "foo", '1.3.3').publish()
-        repo.module("org", "foo", '1.4.4').publish()
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-apply plugin: 'java'
-repositories {
-    maven { url "${repo.uri}" }
-}
-
-dependencies {
-    compile 'org:foo:1.4.4'
-    compile ('org:foo:1.3.3') { force = true }
-}
-
-task checkDeps << {
-    assert configurations.compile*.name == ['foo-1.3.3.jar']
-}
-"""
-
-        //expect
-        executer.withTasks("checkDeps").run()
-    }
-
-    @Test
-    void "forcing transitive dependency does not add extra dependency"() {
-        repo.module("org", "foo", '1.3.3').publish()
-        repo.module("hello", "world", '1.4.4').publish()
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-apply plugin: 'java'
-repositories {
-    maven { url "${repo.uri}" }
-}
-
-dependencies {
-    compile 'org:foo:1.3.3'
-}
-
-configurations.all {
-    resolutionStrategy.force 'hello:world:1.4.4'
-}
-
-task checkDeps << {
-    assert configurations.compile*.name == ['foo-1.3.3.jar']
-}
-"""
-
-        //expect
-        executer.withTasks("checkDeps").run()
-    }
-
-    @Test
-    void "does not attempt to resolve an evicted dependency"() {
-        repo.module("org", "external", "1.2").publish()
-        repo.module("org", "dep", "2.2").dependsOn("org", "external", "1.0").publish()
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-repositories {
-    maven { url "${repo.uri}" }
-}
-
-configurations { compile }
-
-dependencies {
-    compile 'org:external:1.2'
-    compile 'org:dep:2.2'
-}
-
-task checkDeps << {
-    assert configurations.compile*.name == ['external-1.2.jar', 'dep-2.2.jar']
-}
-"""
-
-        //expect
-        executer.withTasks("checkDeps").run()
-    }
-
-    @Test
-    void "resolves dynamic dependency before resolving conflict"() {
-        repo.module("org", "external", "1.2").publish()
-        repo.module("org", "external", "1.4").publish()
-        repo.module("org", "dep", "2.2").dependsOn("org", "external", "1.+").publish()
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-repositories {
-    maven { url "${repo.uri}" }
-}
-
-configurations { compile }
-
-dependencies {
-    compile 'org:external:1.2'
-    compile 'org:dep:2.2'
-}
-
-task checkDeps << {
-    assert configurations.compile*.name == ['dep-2.2.jar', 'external-1.4.jar']
-}
-"""
-
-        //expect
-        executer.withTasks("checkDeps").run()
-    }
-
-    @Test
-    void "fails when version selected by conflict resolution does not exist"() {
-        repo.module("org", "external", "1.2").publish()
-        repo.module("org", "dep", "2.2").dependsOn("org", "external", "1.4").publish()
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-repositories {
-    maven { url "${repo.uri}" }
-}
-
-configurations { compile }
-
-dependencies {
-    compile 'org:external:1.2'
-    compile 'org:dep:2.2'
-}
-
-task checkDeps << {
-    assert configurations.compile*.name == ['external-1.2.jar', 'dep-2.2.jar']
-}
-"""
-
-        //expect
-        def failure = executer.withTasks("checkDeps").runWithFailure()
-        failure.assertHasCause("Could not find group:org, module:external, version:1.4.")
-    }
-
-    @Test
-    void "does not fail when evicted version does not exist"() {
-        repo.module("org", "external", "1.4").publish()
-        repo.module("org", "dep", "2.2").dependsOn("org", "external", "1.4").publish()
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-repositories {
-    maven { url "${repo.uri}" }
-}
-
-configurations { compile }
-
-dependencies {
-    compile 'org:external:1.2'
-    compile 'org:dep:2.2'
-}
-
-task checkDeps << {
-    assert configurations.compile*.name == ['dep-2.2.jar', 'external-1.4.jar']
-}
-"""
-
-        //expect
-        executer.withTasks("checkDeps").run()
-    }
-
-    @Test
-    void "takes newest dynamic version when dynamic version forced"() {
-        //given
-        repo.module("org", "foo", '1.3.0').publish()
-
-        repo.module("org", "foo", '1.4.1').publish()
-        repo.module("org", "foo", '1.4.4').publish()
-        repo.module("org", "foo", '1.4.9').publish()
-
-        repo.module("org", "foo", '1.6.0').publish()
-
-        def settingsFile = file("settings.gradle")
-        settingsFile << "include 'api', 'impl', 'tool'"
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-allprojects {
-	apply plugin: 'java'
-	repositories {
-		maven { url "${repo.uri}" }
-	}
-}
-
-project(':api') {
-	dependencies {
-		compile 'org:foo:1.4.4'
-	}
-}
-
-project(':impl') {
-	dependencies {
-		compile 'org:foo:1.4.1'
-	}
-}
-
-project(':tool') {
-
-	dependencies {
-		compile project(':api'), project(':impl'), 'org:foo:1.3.0'
-	}
-
-	configurations.all {
-	    resolutionStrategy {
-	        force 'org:foo:1.4+'
-	        failOnVersionConflict()
-	    }
-	}
-
-	task checkDeps << {
-        assert configurations.compile*.name.contains('foo-1.4.9.jar')
-    }
-}
-
-"""
-
-        //expect
-        executer.withTasks("tool:checkDeps").run()
-    }
-
-    @Test
-    void "parent pom does not participate in forcing mechanism"() {
-        //given
-        repo.module("org", "foo", '1.3.0').publish()
-        repo.module("org", "foo", '2.4.0').publish()
-
-        def parent = repo.module("org", "someParent", "1.0")
-        parent.type = 'pom'
-        parent.dependsOn("org", "foo", "1.3.0")
-        parent.publish()
-
-        def otherParent = repo.module("org", "someParent", "2.0")
-        otherParent.type = 'pom'
-        otherParent.dependsOn("org", "foo", "2.4.0")
-        otherParent.publish()
-
-        def module = repo.module("org", "someArtifact", '1.0')
-        module.parentPomSection = """
-<parent>
-  <groupId>org</groupId>
-  <artifactId>someParent</artifactId>
-  <version>1.0</version>
-</parent>
-"""
-        module.publish()
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-apply plugin: 'java'
-repositories {
-    maven { url "${repo.uri}" }
-}
-
-dependencies {
-    compile 'org:someArtifact:1.0'
-}
-
-configurations.all {
-    resolutionStrategy {
-        force 'org:someParent:2.0'
-        failOnVersionConflict()
-    }
-}
-
-task checkDeps << {
-    def deps = configurations.compile*.name
-    assert deps.contains('someArtifact-1.0.jar')
-    assert deps.contains('foo-1.3.0.jar')
-    assert deps.size() == 2
-}
-"""
-
-        //expect
-        executer.withTasks("checkDeps").withArguments('-s').run()
-    }
-
-    @Test
-    void "previously evicted nodes should contain correct target version"() {
-        /*
-        a1->b1
-        a2->b2->a1
-
-        resolution process:
-
-        1. stop resolution, resolve conflict a1 vs a2
-        2. select a2, restart resolution
-        3. stop, resolve b1 vs b2
-        4. select b2, restart
-        5. resolve b2 dependencies, a1 has been evicted previously but it should show correctly on the report
-           ('dependencies' report pre 1.2 would not show the a1 dependency leaf for this scenario)
-        */
-
-        ivyRepo.module("org", "b", '1.0').publish()
-        ivyRepo.module("org", "a", '1.0').dependsOn("org", "b", '1.0').publish()
-        ivyRepo.module("org", "b", '2.0').dependsOn("org", "a", "1.0").publish()
-        ivyRepo.module("org", "a", '2.0').dependsOn("org", "b", '2.0').publish()
-
-        file("build.gradle") << """
-            repositories {
-                ivy { url "${ivyRepo.uri}" }
-            }
-
-            configurations {
-                conf
-            }
-            dependencies {
-                conf 'org:a:1.0', 'org:a:2.0'
-            }
-            task checkDeps << {
-                assert configurations.conf*.name == ['a-2.0.jar', 'b-2.0.jar']
-                def result = configurations.conf.incoming.resolutionResult
-                assert result.allModuleVersions.size() == 3
-                def root = result.root
-                assert root.dependencies*.toString() == ['org:a:1.0 -> 2.0', 'org:a:2.0']
-                def a = result.allModuleVersions.find { it.id.name == 'a' }
-                assert a.dependencies*.toString() == ['org:b:2.0']
-                def b = result.allModuleVersions.find { it.id.name == 'b' }
-                assert b.dependencies*.toString() == ['org:a:1.0 -> 2.0']
-            }
-        """
-
-        executer.withTasks("checkDeps").run()
-    }
-
-    @Test
-    @Issue("GRADLE-2555")
-    void "can deal with transitive with parent in conflict"() {
-        /*
-            Graph looks like…
-
-            \--- org:a:1.0
-                 \--- org:in-conflict:1.0 -> 2.0
-                      \--- org:target:1.0
-                           \--- org:target-child:1.0
-            \--- org:b:1.0
-                 \--- org:b-child:1.0
-                      \--- org:in-conflict:2.0 (*)
-
-            This is the simplest structure I could boil it down to that produces the error.
-            - target *must* have a child
-            - Having "b" depend directly on "in-conflict" does not produce the error, needs to go through "b-child"
-         */
-
-        mavenRepo.module("org", "target-child", "1.0").
-                publish()
-
-        mavenRepo.module("org", "target", "1.0").
-                dependsOn("org", "target-child", "1.0").
-                publish()
-
-        mavenRepo.module("org", "in-conflict", "1.0").
-                dependsOn("org", "target", "1.0").
-                publish()
-
-        mavenRepo.module("org", "in-conflict", "2.0").
-                dependsOn("org", "target", "1.0").
-                publish()
-
-        mavenRepo.module("org", "a", '1.0').
-                dependsOn("org", "in-conflict", "1.0").
-                publish()
-
-        mavenRepo.module("org", "b-child", '1.0').
-                dependsOn("org", "in-conflict", "2.0").
-                publish()
-
-        mavenRepo.module("org", "b", '1.0').
-                dependsOn("org", "b-child", "1.0").
-                publish()
-
-        when:
-        file("build.gradle") << """
-            repositories {
-                maven { url "${mavenRepo.uri}" }
-            }
-
-            configurations { conf }
-
-            dependencies {
-                conf "org:a:1.0", "org:b:1.0"
-            }
-
-        task checkDeps << {
-            assert configurations.conf*.name == ['a-1.0.jar', 'b-1.0.jar', 'target-child-1.0.jar', 'target-1.0.jar', 'in-conflict-2.0.jar', 'b-child-1.0.jar']
-            def result = configurations.conf.incoming.resolutionResult
-            assert result.allModuleVersions.size() == 7
-            def a = result.allModuleVersions.find { it.id.name == 'a' }
-            assert a.dependencies*.toString() == ['org:in-conflict:1.0 -> 2.0']
-            def bChild = result.allModuleVersions.find { it.id.name == 'b-child' }
-            assert bChild.dependencies*.toString() == ['org:in-conflict:2.0']
-            def target = result.allModuleVersions.find { it.id.name == 'target' }
-            assert target.dependents*.from*.toString() == ['org:in-conflict:2.0']
-        }
-        """
-
-        executer.withTasks("checkDeps").run()
-    }
-
-    def getRepo() {
-        return maven(file("repo"))
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AliasedArtifactResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AliasedArtifactResolutionIntegrationTest.groovy
deleted file mode 100644
index 5ad6ae5..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AliasedArtifactResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.artifactreuse
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import spock.lang.Ignore
-
-class AliasedArtifactResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-    def mavenRepo1 = mavenHttpRepo("maven1")
-    def mavenRepo2 = mavenHttpRepo("maven2")
-    def ivyRepo1 = ivyHttpRepo("ivy1")
-    def ivyRepo2 = ivyHttpRepo("ivy2")
-
-    def "setup"() {
-        server.start()
-
-        buildFile << """
-            repositories {
-                if (project.hasProperty('mavenRepository1')) {
-                    maven { url '${mavenRepo1.uri}' }
-                } else if (project.hasProperty('mavenRepository2')) {
-                    maven { url '${mavenRepo2.uri}' }
-                } else if (project.hasProperty('ivyRepository1')) {
-                    ivy { url '${ivyRepo1.uri}' }
-                } else if (project.hasProperty('ivyRepository2')) {
-                    ivy { url '${ivyRepo2.uri}' }
-                } else if (project.hasProperty('fileRepository')) {
-                    maven { url '${mavenRepo.uri}' }
-                }
-            }
-            configurations { compile }
-            dependencies {
-                compile 'org.name:projectB:1.0'
-            }
-
-            task retrieve(type: Sync) {
-                into 'libs'
-                from configurations.compile
-            }
-        """
-    }
-
-    def "does not re-download maven artifact downloaded from a different maven repository when sha1 matches"() {
-        when:
-        def projectBRepo1 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
-        projectBRepo1.expectPomGet()
-        projectBRepo1.expectArtifactGet()
-
-        then:
-        succeedsWith 'mavenRepository1'
-
-        when:
-        def projectBRepo2 = mavenRepo2.module('org.name', 'projectB', '1.0').publish()
-        projectBRepo2.expectPomHead()
-        projectBRepo2.expectPomSha1Get()
-        projectBRepo2.expectArtifactHead()
-        projectBRepo2.expectArtifactSha1Get()
-
-        then:
-        succeedsWith 'mavenRepository2'
-    }
-
-    def "does not re-download ivy artifact downloaded from a different ivy repository when sha1 matches"() {
-        when:
-        def projectBRepo1 = ivyRepo1.module('org.name', 'projectB', '1.0').publish()
-        projectBRepo1.expectIvyGet()
-        projectBRepo1.expectJarGet()
-
-        then:
-        succeedsWith 'ivyRepository1'
-
-        when:
-        def projectBRepo2 = ivyRepo2.module('org.name', 'projectB', '1.0').publish()
-        projectBRepo2.expectIvyHead()
-        projectBRepo2.expectIvySha1Get()
-        projectBRepo2.expectJarHead()
-        projectBRepo2.expectJarSha1Get()
-
-        then:
-        succeedsWith 'ivyRepository2'
-    }
-
-    def "does not re-download ivy artifact downloaded from a maven repository when sha1 matches"() {
-        when:
-        def projectBRepo1 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
-        projectBRepo1.expectPomGet()
-        projectBRepo1.expectArtifactGet()
-
-        then:
-        succeedsWith 'mavenRepository1'
-
-        when:
-        def projectBRepo2 = ivyRepo1.module('org.name', 'projectB', '1.0').publish()
-        projectBRepo2.expectIvyGet()
-        projectBRepo2.expectJarHead()
-        projectBRepo2.expectJarSha1Get()
-
-        then:
-        succeedsWith 'ivyRepository1'
-    }
-
-    def "does not re-download maven artifact downloaded from a ivy repository when sha1 matches"() {
-        when:
-        def projectBRepo1 = ivyRepo1.module('org.name', 'projectB', '1.0').publish()
-        projectBRepo1.expectIvyGet()
-        projectBRepo1.expectJarGet()
-
-        then:
-        succeedsWith 'ivyRepository1'
-
-        when:
-        def projectBRepo2 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
-        projectBRepo2.expectPomGet()
-        projectBRepo2.expectArtifactHead()
-        projectBRepo2.expectArtifactSha1Get()
-
-        then:
-        succeedsWith 'mavenRepository1'
-    }
-
-    @Ignore("File repository does not cache artifacts locally, so they are not used to prevent download")
-    def "does not download artifact previously accessed from a file uri when sha1 matches"() {
-        given:
-        succeedsWith 'fileRepository'
-
-        when:
-        def projectBRepo2 = mavenRepo2.module('org.name', 'projectB', '1.0').publish()
-        projectBRepo2.expectPomHead()
-        projectBRepo2.expectPomSha1Get()
-        projectBRepo2.expectArtifactHead()
-        projectBRepo2.expectArtifactSha1Get()
-
-        then:
-        succeedsWith 'mavenRepository2'
-    }
-
-    def "does re-download maven artifact downloaded from a different URI when sha1 not found"() {
-        when:
-        def projectBRepo1 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
-        projectBRepo1.expectPomGet()
-        projectBRepo1.expectArtifactGet()
-
-        then:
-        succeedsWith 'mavenRepository1'
-
-        when:
-        def projectBRepo2 = mavenRepo2.module('org.name', 'projectB', '1.0').publish()
-        projectBRepo2.expectPomHead()
-        projectBRepo2.expectPomSha1GetMissing()
-        projectBRepo2.expectPomGet()
-        projectBRepo2.expectArtifactHead()
-        projectBRepo2.expectArtifactSha1GetMissing()
-        projectBRepo2.expectArtifactGet()
-
-        then:
-        succeedsWith 'mavenRepository2'
-    }
-
-    def "does re-download maven artifact downloaded from a different URI when sha1 does not match"() {
-        when:
-        def projectBRepo1 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
-        projectBRepo1.expectPomGet()
-        projectBRepo1.expectArtifactGet()
-
-        then:
-        succeedsWith 'mavenRepository1'
-
-        when:
-        def projectBRepo2 = mavenRepo2.module('org.name', 'projectB', '1.0').publishWithChangedContent()
-        projectBRepo2.expectPomHead()
-        projectBRepo2.expectPomSha1Get()
-        projectBRepo2.expectPomGet()
-        projectBRepo2.expectArtifactHead()
-        projectBRepo2.expectArtifactSha1Get()
-        projectBRepo2.expectArtifactGet()
-
-        then:
-        succeedsWith 'mavenRepository2'
-    }
-
-    def succeedsWith(repository) {
-        executer.withArguments('-i', "-P${repository}")
-        def result = succeeds 'retrieve'
-        file('libs').assertHasDescendants('projectB-1.0.jar')
-        server.resetExpectations()
-        return result
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/CacheReuseCrossVersionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/CacheReuseCrossVersionIntegrationTest.groovy
deleted file mode 100644
index da91b51..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/CacheReuseCrossVersionIntegrationTest.groovy
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.artifactreuse
-
-import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
-import org.gradle.integtests.fixtures.MavenFileRepository
-import org.gradle.integtests.fixtures.MavenHttpRepository
-import org.gradle.integtests.fixtures.TargetVersions
-import org.gradle.test.fixtures.server.http.HttpServer
-import org.junit.Rule
-
- at TargetVersions('1.0-milestone-6+')
-class CacheReuseCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
-    @Rule public final HttpServer server = new HttpServer()
-    final MavenHttpRepository httpRepo = new MavenHttpRepository(server, new MavenFileRepository(file("maven-repo")))
-
-    def "uses cached artifacts from previous Gradle version when no sha1 header"() {
-        given:
-        def projectB = httpRepo.module('org.name', 'projectB', '1.0').publish()
-        server.sendSha1Header = false
-        server.start()
-        buildFile << """
-repositories {
-    maven { url '${httpRepo.uri}' }
-}
-configurations { compile }
-dependencies {
-    compile 'org.name:projectB:1.0'
-}
-
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-        and:
-        def userHome = file('user-home')
-
-        when:
-        projectB.allowAll()
-
-        and:
-        version previous withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
-
-        then:
-        file('libs').assertHasDescendants('projectB-1.0.jar')
-        def snapshot = file('libs/projectB-1.0.jar').snapshot()
-
-        when:
-        server.resetExpectations()
-        projectB.allowPomHead()
-        projectB.allowPomSha1Get()
-        projectB.allowArtifactHead()
-        projectB.allowArtifactSha1Get()
-
-        and:
-        version current withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
-
-        then:
-        file('libs').assertHasDescendants('projectB-1.0.jar')
-        file('libs/projectB-1.0.jar').assertContentsHaveNotChangedSince(snapshot)
-    }
-
-    def "uses cached artifacts from previous Gradle version with sha1 header"() {
-        given:
-        def projectB = httpRepo.module('org.name', 'projectB', '1.0').publish()
-        server.sendSha1Header = true
-        server.start()
-        buildFile << """
-repositories {
-    maven { url '${httpRepo.uri}' }
-}
-configurations { compile }
-dependencies {
-    compile 'org.name:projectB:1.0'
-}
-
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-        and:
-        def userHome = file('user-home')
-
-        when:
-        projectB.allowAll()
-
-        and:
-        version previous withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
-
-        then:
-        file('libs').assertHasDescendants('projectB-1.0.jar')
-        def snapshot = file('libs/projectB-1.0.jar').snapshot()
-
-        when:
-        server.resetExpectations()
-        projectB.allowPomHead()
-        projectB.allowArtifactHead()
-
-        and:
-        version current withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
-
-        then:
-        file('libs').assertHasDescendants('projectB-1.0.jar')
-        file('libs/projectB-1.0.jar').assertContentsHaveNotChangedSince(snapshot)
-    }
-
-    def "uses cached artifacts from previous Gradle version that match dynamic version"() {
-        given:
-        def projectB = httpRepo.module('org.name', 'projectB', '1.1').publish()
-        server.start()
-
-        buildFile << """
-repositories {
-    maven { url '${httpRepo.uri}' }
-}
-configurations { compile }
-dependencies {
-    compile 'org.name:projectB:[1.0,2.0]'
-}
-
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-        and:
-        def userHome = file('user-home')
-
-        when:
-        httpRepo.expectMetaDataGet("org.name", "projectB")
-        projectB.allowAll()
-
-        and:
-        version previous withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
-
-        then:
-        file('libs').assertHasDescendants('projectB-1.1.jar')
-        def snapshot = file('libs/projectB-1.1.jar').snapshot()
-
-        when:
-        server.resetExpectations()
-        httpRepo.allowMetaDataGet("org.name", "projectB")
-        projectB.allowPomHead()
-        projectB.allowPomSha1Get()
-        projectB.allowArtifactHead()
-        projectB.allowArtifactSha1Get()
-
-        and:
-        version current withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
-
-        then:
-        file('libs').assertHasDescendants('projectB-1.1.jar')
-        file('libs/projectB-1.1.jar').assertContentsHaveNotChangedSince(snapshot)
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/M3CacheReuseCrossVersionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/M3CacheReuseCrossVersionIntegrationTest.groovy
deleted file mode 100644
index 87433ec..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/M3CacheReuseCrossVersionIntegrationTest.groovy
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.artifactreuse
-
-import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
-import org.gradle.integtests.fixtures.MavenHttpRepository
-import org.gradle.integtests.fixtures.TargetVersions
-import org.gradle.test.fixtures.server.http.HttpServer
-import org.junit.Rule
-
-// TODO:DAZ Support for milestone-3 does not include POM reuse. We should probably ditch milestone-3 support after 1.0.
- at TargetVersions('1.0-milestone-3')
-class M3CacheReuseCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
-    @Rule public final HttpServer server = new HttpServer()
-    final MavenHttpRepository remoteRepo = new MavenHttpRepository(server, mavenRepo)
-
-    def "uses cached artifacts from previous Gradle version"() {
-        given:
-        def projectB = remoteRepo.module('org.name', 'projectB').publish()
-
-        server.start()
-        buildFile << """
-repositories {
-    mavenRepo(urls: ['${remoteRepo.uri}'])
-}
-configurations { compile }
-dependencies {
-    compile 'org.name:projectB:1.0'
-}
-
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-        and:
-        def userHome = file('user-home')
-
-        when:
-        projectB.allowAll()
-
-        and:
-        version previous withGradleUserHomeDir userHome withTasks 'retrieve' run()
-
-        then:
-        file('libs').assertHasDescendants('projectB-1.0.jar')
-        def snapshot = file('libs/projectB-1.0.jar').snapshot()
-
-        when:
-        server.resetExpectations()
-        projectB.expectPomGet()
-        projectB.expectArtifactHead()
-        projectB.expectArtifactSha1Get()
-
-        and:
-        version current withGradleUserHomeDir userHome withTasks 'retrieve' run()
-
-        then:
-        file('libs').assertHasDescendants('projectB-1.0.jar')
-        file('libs/projectB-1.0.jar').assertContentsHaveNotChangedSince(snapshot)
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenM2CacheReuseIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenM2CacheReuseIntegrationTest.groovy
deleted file mode 100644
index d738a3e..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenM2CacheReuseIntegrationTest.groovy
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.artifactreuse
-
-import org.gradle.integtests.fixture.M2Installation
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class MavenM2CacheReuseIntegrationTest extends AbstractDependencyResolutionTest {
-    def "uses cached artifacts from maven local cache"() {
-        given:
-        def module1 = mavenHttpRepo.module('gradletest.maven.local.cache.test', "foo", "1.0").publish()
-        def m2 = new M2Installation(testDir).generateGlobalSettingsFile()
-        def module2 = m2.mavenRepo().module('gradletest.maven.local.cache.test', "foo", "1.0").publish()
-        server.start()
-
-        buildFile.text = """
-repositories {
-    maven { url "${mavenHttpRepo.uri}" }
-}
-configurations { compile }
-dependencies {
-    compile 'gradletest.maven.local.cache.test:foo:1.0'
-}
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'build'
-}
-"""
-        and:
-        module1.expectPomHead()
-        module1.expectPomSha1Get()
-        module1.expectArtifactHead()
-        module1.expectArtifactSha1Get()
-
-        when:
-        executer.withEnvironmentVars(M2_HOME: m2.globalMavenDirectory)
-        run 'retrieve'
-
-        then:
-        file('build/foo-1.0.jar').assertIsCopyOf(module2.artifactFile)
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/ResolutionOverrideIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/ResolutionOverrideIntegrationTest.groovy
deleted file mode 100644
index 5be3148..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/ResolutionOverrideIntegrationTest.groovy
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.artifactreuse
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import org.hamcrest.Matchers
-
-class ResolutionOverrideIntegrationTest extends AbstractDependencyResolutionTest {
-    public void "will refresh non-changing module when run with --refresh-dependencies"() {
-        given:
-        server.start()
-        def module = mavenHttpRepo.module('org.name', 'projectA', '1.2').publish()
-
-        and:
-        buildFile << """
-repositories {
-    maven { url "${mavenHttpRepo.uri}" }
-}
-configurations { compile }
-dependencies { compile 'org.name:projectA:1.2' }
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-        and:
-        module.allowAll()
-
-        when:
-        succeeds 'retrieve'
-        
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-        def snapshot = file('libs/projectA-1.2.jar').snapshot()
-
-        when:
-        module.publishWithChangedContent()
-
-        and:
-        executer.withArguments('--refresh-dependencies')
-        succeeds 'retrieve'
-        
-        then:
-        file('libs/projectA-1.2.jar').assertIsCopyOf(module.artifactFile).assertHasChangedSince(snapshot)
-    }
-
-    public void "will recover from missing module when run with --refresh-dependencies"() {
-        server.start()
-
-        given:
-        def module = mavenHttpRepo.module('org.name', 'projectA', '1.2').publish()
-
-        buildFile << """
-repositories {
-    maven {
-        url "${mavenHttpRepo.uri}"
-    }
-}
-configurations { missing }
-dependencies {
-    missing 'org.name:projectA:1.2'
-}
-task showMissing << { println configurations.missing.files }
-"""
-
-        when:
-        module.expectPomGetMissing()
-        module.expectArtifactHeadMissing()
-
-        then:
-        fails("showMissing")
-
-        when:
-        server.resetExpectations()
-        module.expectPomGet()
-        module.expectArtifactGet()
-
-        then:
-        executer.withArguments("--refresh-dependencies")
-        succeeds('showMissing')
-    }
-
-    public void "will recover from missing artifact when run with --refresh-dependencies"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    maven {
-        url "${mavenHttpRepo.uri}"
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'org.name:projectA:1.2'
-}
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        and:
-        def module = mavenHttpRepo.module('org.name', 'projectA', '1.2').publish()
-
-        when:
-        module.expectPomGet()
-        module.expectArtifactGetMissing()
-
-        then:
-        fails "retrieve"
-
-        when:
-        server.resetExpectations()
-        module.expectPomHead()
-        module.expectArtifactGet()
-
-        then:
-        executer.withArguments("--refresh-dependencies")
-        succeeds 'retrieve'
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-    }
-
-    public void "will not expire cache entries when run with offline flag"() {
-
-        given:
-        server.start()
-        def module = mavenHttpRepo.module("org.name", "unique", "1.0-SNAPSHOT").publish()
-
-        and:
-        buildFile << """
-repositories {
-    maven { url "${mavenHttpRepo.uri}" }
-}
-configurations { compile }
-configurations.all {
-    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-}
-dependencies {
-    compile "org.name:unique:1.0-SNAPSHOT"
-}
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-
-        when:  "Server handles requests"
-        module.allowAll()
-
-        and: "We resolve dependencies"
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar')
-        def snapshot = file('libs/unique-1.0-SNAPSHOT.jar').snapshot()
-
-        when:
-        module.publishWithChangedContent()
-
-        and: "We resolve again, offline"
-        server.resetExpectations()
-        executer.withArguments('--offline')
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar')
-        file('libs/unique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshot)
-    }
-
-    public void "does not attempt to contact server when run with offline flag"() {
-        given:
-        server.start()
-
-        and:
-        buildFile << """
-repositories {
-    maven { url "${mavenHttpRepo.uri}" }
-}
-configurations { compile }
-dependencies { compile 'org.name:projectA:1.2' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-
-        when:
-        executer.withArguments("--offline")
-
-        then:
-        fails 'listJars'
-
-        and:
-        failure.assertHasDescription('Execution failed for task \':listJars\'.')
-        failure.assertHasCause('Could not resolve all dependencies for configuration \':compile\'.')
-        failure.assertThatCause(Matchers.containsString('No cached version available for offline mode'))
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedDependencyResolutionIntegrationTest.groovy
deleted file mode 100644
index 1916ac0..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedDependencyResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.resolve.caching
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import org.gradle.test.fixtures.ivy.IvyHttpModule
-import org.gradle.test.fixtures.server.http.HttpServer
-import org.gradle.util.TestFile
-
-/**
- * We are using Ivy here, but the strategy is the same for any kind of repository.
- */
-class CachedDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-
-    IvyHttpModule module
-
-    TestFile downloaded
-    TestFile.Snapshot lastState
-
-    def setup() {
-        server.start()
-        buildFile << """
-repositories {
-    ivy { url "${ivyHttpRepo.uri}" }
-}
-
-configurations { compile }
-
-configurations.all {
-    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-}
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.1", changing: true
-}
-
-task retrieve(type: Sync) {
-    into 'build'
-    from configurations.compile
-}
-"""
-
-        module = ivyHttpRepo.module("group", "projectA", "1.1").publish()
-
-        downloaded = file('build/projectA-1.1.jar')
-    }
-
-    void initialResolve() {
-        module.expectIvyGet()
-        module.expectJarGet()
-
-        resolve()
-    }
-
-    void resolve() {
-        if (downloaded.exists()) {
-            lastState = downloaded.snapshot()
-        }
-
-        succeeds ":retrieve"
-    }
-
-    void headOnlyRequests() {
-        module.expectIvyHead()
-        module.expectJarHead()
-    }
-
-    void headSha1ThenGetRequests() {
-        module.expectIvyHead()
-        module.expectIvySha1Get()
-        module.expectIvyGet()
-
-        module.expectJarHead()
-        module.expectJarSha1Get()
-        module.expectJarGet()
-    }
-
-    void sha1OnlyRequests() {
-        module.expectIvySha1Get()
-        module.expectJarSha1Get()
-    }
-
-    void sha1ThenGetRequests() {
-        module.expectIvySha1Get()
-        module.expectIvyGet()
-
-        module.expectJarSha1Get()
-        module.expectJarGet()
-    }
-
-    void headThenSha1Requests() {
-        module.expectIvyHead()
-        module.expectIvySha1Get()
-
-        module.expectJarHead()
-        module.expectJarSha1Get()
-    }
-
-    void headThenGetRequests() {
-        module.expectIvyHead()
-        module.expectIvyGet()
-
-        module.expectJarHead()
-        module.expectJarGet()
-    }
-
-    void unchangedResolve() {
-        resolve()
-        downloaded.assertHasNotChangedSince(lastState)
-    }
-
-    void changedResolve() {
-        resolve()
-        downloaded.assertHasChangedSince(lastState)
-    }
-
-    void change() {
-        module.publishWithChangedContent()
-    }
-
-    def "etags are used to determine changed"() {
-        given:
-        server.etags = HttpServer.EtagStrategy.RAW_SHA1_HEX
-        server.sendLastModified = false
-        initialResolve()
-
-        expect:
-        headOnlyRequests()
-        unchangedResolve()
-
-        when:
-        change()
-
-        then:
-        headSha1ThenGetRequests()
-        changedResolve()
-    }
-
-    def "last modified and content length are used to determine changed"() {
-        given:
-        server.etags = null
-        initialResolve()
-
-        expect:
-        headOnlyRequests()
-        unchangedResolve()
-
-        when:
-        change()
-
-        then:
-        headSha1ThenGetRequests()
-        changedResolve()
-    }
-
-    def "checksum is used when last modified and content length can't be used"() {
-        given:
-        server.etags = null
-        server.sendLastModified = false
-        initialResolve()
-
-        expect:
-        headThenSha1Requests()
-        unchangedResolve()
-
-        when:
-        change()
-
-        then:
-        headSha1ThenGetRequests()
-        changedResolve()
-    }
-
-    def "no need for sha1 request if we get it in the metadata"() {
-        given:
-        server.sendSha1Header = true
-        initialResolve()
-
-        expect:
-        headOnlyRequests()
-        unchangedResolve()
-
-        when:
-        change()
-
-        then:
-        headThenGetRequests()
-        changedResolve()
-    }
-
-    def "no need for sha1 request if we know the etag is sha1"() {
-        given:
-        server.etags = HttpServer.EtagStrategy.NEXUS_ENCODED_SHA1
-        initialResolve()
-
-        expect:
-        headOnlyRequests()
-        unchangedResolve()
-
-        when:
-        change()
-
-        then:
-        headThenGetRequests()
-        changedResolve()
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedMissingModulesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedMissingModulesIntegrationTest.groovy
deleted file mode 100644
index 56878b0..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedMissingModulesIntegrationTest.groovy
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.resolve.caching
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import spock.lang.Ignore
-import spock.lang.IgnoreIf
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-
-class CachedMissingModulesIntegrationTest extends AbstractDependencyResolutionTest {
-
-    def "cached not-found information for dynamic version is ignored if module is not available in any repo"() {
-        given:
-        server.start()
-        def repo1 = mavenHttpRepo("repo1")
-        repo1.module("group", "projectA", "1.0")
-        def repo2 = mavenHttpRepo("repo2")
-        def repo2Module = repo2.module("group", "projectA", "1.0")
-
-        buildFile << """
-            repositories {
-                maven {
-                    name 'repo1'
-                    url '${repo1.uri}'
-                }
-                maven {
-                    name 'repo2'
-                    url '${repo2.uri}'
-                }
-            }
-            configurations { compile }
-            dependencies {
-                compile 'group:projectA:latest.integration'
-            }
-
-            task retrieve(type: Sync) {
-                into 'libs'
-                from configurations.compile
-            }
-            """
-
-        when:
-        repo1.expectMetaDataGetMissing("group", "projectA")
-        repo1.expectMetaDataGetMissing("group", "projectA")
-        repo1.expectDirectoryListGet("group", "projectA")
-        repo1.expectDirectoryListGet("group", "projectA")
-        repo2.expectMetaDataGetMissing("group", "projectA")
-        repo2.expectMetaDataGetMissing("group", "projectA")
-        repo2.expectDirectoryListGet("group", "projectA")
-        repo2.expectDirectoryListGet("group", "projectA")
-
-        then:
-        runAndFail 'retrieve'
-
-        when:
-        server.resetExpectations()
-        repo1.expectMetaDataGetMissing("group", "projectA")
-        repo1.expectMetaDataGetMissing("group", "projectA")
-        repo1.expectDirectoryListGet("group", "projectA")
-        repo1.expectDirectoryListGet("group", "projectA")
-        repo2Module.publish()
-        repo2.expectMetaDataGet("group", "projectA")
-        repo2Module.expectPomGet()
-        repo2Module.expectArtifactGet()
-
-        then:
-        run 'retrieve'
-
-        when:
-        server.resetExpectations()
-
-        then:
-        run 'retrieve'
-    }
-
-    def "cached not-found information for fixed version is ignored if module is not available in any repo"() {
-        given:
-        server.start()
-        def repo1 = mavenHttpRepo("repo1")
-        def repo1Module = repo1.module("group", "projectA", "1.0")
-        def repo2 = mavenHttpRepo("repo2")
-        def repo2Module = repo2.module("group", "projectA", "1.0")
-
-        buildFile << """
-    repositories {
-        maven {
-            name 'repo1'
-            url '${repo1.uri}'
-        }
-        maven {
-            name 'repo2'
-            url '${repo2.uri}'
-        }
-    }
-    configurations { compile }
-    dependencies {
-        compile 'group:projectA:1.0'
-    }
-
-    task retrieve(type: Sync) {
-        into 'libs'
-        from configurations.compile
-    }
-    """
-
-        when:
-        repo1Module.expectPomGetMissing()
-        repo1Module.expectArtifactHeadMissing()
-        repo2Module.expectPomGetMissing()
-        repo2Module.expectArtifactHeadMissing()
-
-        then:
-        runAndFail 'retrieve'
-
-        when:
-        server.resetExpectations()
-        repo1Module.expectPomGetMissing()
-        repo1Module.expectArtifactHeadMissing()
-        repo2Module.publish()
-        repo2Module.expectPomGet()
-        repo2Module.expectArtifactGet()
-
-        then:
-        run 'retrieve'
-
-        when:
-        server.resetExpectations()
-
-        then:
-        run 'retrieve'
-    }
-
-    @IgnoreIf({ GradleDistributionExecuter.systemPropertyExecuter.executeParallel })
-    def "hit each remote repo only once per build and missing module"() {
-        given:
-        server.start()
-        def repo1 = mavenHttpRepo("repo1")
-        def repo1Module = repo1.module("group", "projectA", "1.0")
-        def repo2 = mavenHttpRepo("repo2")
-        def repo2Module = repo2.module("group", "projectA", "1.0")
-
-        settingsFile << "include 'subproject'"
-        buildFile << """
-            allprojects{
-                repositories {
-                    maven {
-                        name 'repo1'
-                        url '${repo1.uri}'
-                    }
-                    maven {
-                        name 'repo2'
-                        url '${repo2.uri}'
-                    }
-                }
-            }
-            configurations {
-                config1
-            }
-            dependencies {
-                config1 'group:projectA:1.0'
-            }
-
-            task resolveConfig1 << {
-                   configurations.config1.incoming.resolutionResult.allDependencies{
-                        it instanceof UnresolvedDependencyResult
-                   }
-            }
-
-            project(":subproject"){
-                configurations{
-                    config2
-                }
-                dependencies{
-                    config2 'group:projectA:1.0'
-                }
-                task resolveConfig2 << {
-                    configurations.config2.incoming.resolutionResult.allDependencies{
-                        it instanceof UnresolvedDependencyResult
-                    }
-                }
-            }
-        """
-        when:
-        repo1Module.expectPomGetMissing()
-        repo1Module.expectArtifactHeadMissing()
-        repo2Module.expectPomGetMissing()
-        repo2Module.expectArtifactHeadMissing()
-
-        then:
-        run('resolveConfig1')
-
-        when:
-        server.resetExpectations()
-        repo1Module.expectPomGetMissing()
-        repo1Module.expectArtifactHeadMissing()
-        repo2Module.expectPomGetMissing()
-        repo2Module.expectArtifactHeadMissing()
-
-        then:
-        run "resolveConfig1", "resolveConfig2"
-    }
-
-    def "does not hit remote repositories if version is available in local repo"() {
-        given:
-        server.start()
-        def repo1 = mavenHttpRepo("repo1")
-        def repo1Module = repo1.module("group", "projectA", "1.0")
-        def repo2 = mavenRepo("repo2")
-        def repo2Module = repo2.module("group", "projectA", "1.0")
-
-        buildFile << """
-        repositories {
-           maven {
-               name 'repo1'
-               url '${repo1.uri}'
-           }
-           maven {
-               name 'repo2'
-               url '${repo2.uri}'
-           }
-       }
-       configurations { compile }
-       dependencies {
-           compile 'group:projectA:1.0'
-       }
-
-       task retrieve(type: Sync) {
-           into 'libs'
-           from configurations.compile
-       }
-       """
-
-        when:
-        repo2Module.publish()
-        repo1Module.expectPomGetMissing()
-        repo1Module.expectArtifactHeadMissing()
-
-        then:
-        run 'retrieve'
-
-        when:
-        server.resetExpectations()
-        then:
-        run 'retrieve'
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvySFtpResolverIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvySFtpResolverIntegrationTest.groovy
deleted file mode 100644
index 845b8fa..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvySFtpResolverIntegrationTest.groovy
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.custom
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.ProgressLoggingFixture
-import org.gradle.test.fixtures.ivy.IvyRepository
-import org.gradle.test.fixtures.server.sftp.SFTPServer
-import org.junit.Rule
-
-class IvySFtpResolverIntegrationTest extends AbstractIntegrationSpec {
-
-    @Rule
-    public final SFTPServer server = new SFTPServer(distribution.temporaryFolder)
-
-    @Rule ProgressLoggingFixture progressLogging
-
-    def "setup"() {
-        requireOwnUserHomeDir()
-    }
-
-    public void "can resolve and cache dependencies from an SFTP Ivy repository"() {
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.2')
-        module.publish();
-
-        and:
-        buildFile << """
-repositories {
-    add(new org.apache.ivy.plugins.resolver.SFTPResolver()) {
-        name = "sftprepo"
-        host = "${server.hostAddress}"
-        port = ${server.port}
-        user = "simple"
-        userPassword = "simple"
-        addIvyPattern "repos/libs/[organization]/[module]/[revision]/ivy-[revision].xml"
-        addArtifactPattern "repos/libs/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
-    }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.2' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-        when:
-        succeeds 'listJars'
-
-        then:
-        server.fileRequests == ["repos/libs/group/projectA/1.2/ivy-1.2.xml",
-                "repos/libs/group/projectA/1.2/projectA-1.2.jar"
-        ] as Set
-
-        progressLogging.downloadProgressLogged("repos/libs/group/projectA/1.2/ivy-1.2.xml")
-        progressLogging.downloadProgressLogged("repos/libs/group/projectA/1.2/projectA-1.2.jar")
-
-        when:
-        server.clearRequests()
-        succeeds 'listJars'
-
-        then:
-        server.fileRequests.empty
-    }
-
-    IvyRepository ivyRepo() {
-        return ivy(server.file("repos/libs/"))
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvyUrlResolverIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvyUrlResolverIntegrationTest.groovy
deleted file mode 100644
index c0eb839..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvyUrlResolverIntegrationTest.groovy
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.custom
-
-import org.gradle.integtests.fixtures.ProgressLoggingFixture
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import org.junit.Rule
-
-class IvyUrlResolverIntegrationTest extends AbstractDependencyResolutionTest {
-
-    @Rule ProgressLoggingFixture progressLogging
-
-    def setup() {
-        server.expectUserAgent(null) // custom resolver uses apache/ivy as useragent strings
-    }
-
-    public void "can resolve and cache dependencies from an HTTP Ivy repository"() {
-        server.start()
-        given:
-        def module = ivyHttpRepo.module('group', 'projectA', '1.2').publish()
-
-        and:
-        buildFile << """
-repositories {
-    add(new org.apache.ivy.plugins.resolver.URLResolver()) {
-        name = "repo"
-        addIvyPattern("${ivyHttpRepo.ivyPattern}")
-        addArtifactPattern("${ivyHttpRepo.artifactPattern}")
-    }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.2' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-        when:
-        module.expectIvyHead()
-        module.expectIvyGet()
-        module.expectJarHead()
-        module.expectJarGet()
-
-        then:
-        succeeds 'listJars'
-
-        and:
-        progressLogging.downloadProgressLogged(module.ivyFileUri)
-        progressLogging.downloadProgressLogged(module.jarFileUri)
-
-        when:
-        server.resetExpectations()
-
-        // No extra calls for cached dependencies
-        then:
-        succeeds 'listJars'
-    }
-
-    public void "honours changing patterns from custom resolver"() {
-        server.start()
-        given:
-        def module = ivyHttpRepo.module('group', 'projectA', '1.2-SNAPSHOT').publish()
-
-        and:
-        buildFile << """
-repositories {
-    add(new org.apache.ivy.plugins.resolver.URLResolver()) {
-        name = "repo"
-        addIvyPattern("${ivyHttpRepo.ivyPattern}")
-        addArtifactPattern("${ivyHttpRepo.artifactPattern}")
-        changingMatcher = 'regexp'
-        changingPattern = '.*SNAPSHOT.*'
-    }
-}
-configurations { compile }
-configurations.compile.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-dependencies { compile 'group:projectA:1.2-SNAPSHOT' }
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-        when:
-        module.expectIvyHead()
-        module.expectIvyGet()
-        module.expectJarHead()
-        module.expectJarGet()
-
-        run 'retrieve'
-
-        then:
-        def jarFile = file('libs/projectA-1.2-SNAPSHOT.jar')
-        jarFile.assertIsCopyOf(module.jarFile)
-        def snapshot = jarFile.snapshot()
-
-        when:
-        module.publishWithChangedContent()
-
-        server.resetExpectations()
-        // Server will be hit to get updated versions
-        module.expectIvyHead()
-        module.expectIvyGet()
-        module.expectJarHead()
-        module.expectJarGet()
-
-        run 'retrieve'
-
-        then:
-        def changedJarFile = file('libs/projectA-1.2-SNAPSHOT.jar')
-        changedJarFile.assertHasChangedSince(snapshot)
-        changedJarFile.assertIsCopyOf(module.jarFile)
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest.groovy
deleted file mode 100644
index c43372a..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.resolve.http
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import org.gradle.integtests.fixtures.TestResources
-import org.junit.Rule
-
-import static org.gradle.util.Matchers.containsText
-
-abstract class AbstractHttpsRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
-    @Rule TestResources resources
-    File clientStore // contains the client's public and private keys
-    File serverStore // contains the server's public and private keys
-
-    abstract protected String setupRepo()
-
-    def "resolve with server certificate"() {
-        setupCertStores()
-        server.enableSsl(serverStore.path, "asdfgh")
-        server.start()
-
-        def repoType = setupRepo()
-        setupBuildFile(repoType)
-
-        when:
-        executer.withArgument("-Djavax.net.ssl.trustStore=$serverStore.path")
-                .withArgument("-Djavax.net.ssl.trustStorePassword=asdfgh")
-                .withTasks('libs').run()
-
-        then:
-        file('libs').assertHasDescendants('my-module-1.0.jar')
-    }
-
-    def "resolve with server and client certificate"() {
-        setupCertStores()
-        server.enableSsl(serverStore.path, "asdfgh", clientStore.path, "asdfgh")
-        server.start()
-
-        def repoType = setupRepo()
-        setupBuildFile(repoType)
-
-        when:
-        executer.withArgument("-Djavax.net.ssl.trustStore=$serverStore.path")
-                .withArgument("-Djavax.net.ssl.trustStorePassword=asdfgh")
-                .withArgument("-Djavax.net.ssl.keyStore=$clientStore.path")
-                .withArgument("-Djavax.net.ssl.keyStorePassword=asdfgh")
-                .withTasks('libs').run()
-
-        then:
-        file('libs').assertHasDescendants('my-module-1.0.jar')
-    }
-
-    def "decent error message when client can't authenticate server"() {
-        setupCertStores()
-        server.enableSsl(serverStore.path, "asdfgh")
-        server.start()
-
-        def repoType = setupRepo()
-        setupBuildFile(repoType)
-
-        when:
-        def failure = executer.withStackTraceChecksDisabled() // Jetty logs stuff to console
-                .withArgument("-Djavax.net.ssl.trustStore=$clientStore.path") // intentionally use wrong trust store for client
-                .withArgument("-Djavax.net.ssl.trustStorePassword=asdfgh")
-                .withTasks('libs').runWithFailure()
-
-        then:
-        failure.assertThatCause(containsText("Could not GET 'https://localhost:(\\d*)/repo1/my-group/my-module/1.0/"))
-        failure.assertHasCause("peer not authenticated")
-    }
-
-    def "decent error message when server can't authenticate client"() {
-        setupCertStores()
-        server.enableSsl(serverStore.path, "asdfgh", serverStore.path, "asdfgh") // intentionally use wrong trust store for server
-        server.start()
-
-        def repoType = setupRepo()
-        setupBuildFile(repoType)
-
-        when:
-        def failure = executer.withStackTraceChecksDisabled() // Jetty logs stuff to console
-                .withArgument("-Djavax.net.ssl.trustStore=$serverStore.path")
-                .withArgument("-Djavax.net.ssl.trustStorePassword=asdfgh")
-                .withArgument("-Djavax.net.ssl.keyStore=$clientStore.path")
-                .withArgument("-Djavax.net.ssl.keyStorePassword=asdfgh")
-                .withTasks('libs').runWithFailure()
-
-        then:
-        failure.assertThatCause(containsText("Could not GET 'https://localhost:(\\d*)/repo1/my-group/my-module/1.0/"))
-        failure.assertHasCause("peer not authenticated")
-    }
-
-    def setupCertStores() {
-        clientStore = resources.dir.file("clientStore")
-        serverStore = resources.dir.file("serverStore")
-    }
-
-    private void setupBuildFile(String repoType) {
-        buildFile << """
-repositories {
-    $repoType { url 'https://localhost:${server.sslPort}/repo1' }
-}
-configurations { compile }
-dependencies {
-    compile 'my-group:my-module:1.0'
-}
-task libs(type: Copy) {
-    into 'libs'
-    from configurations.compile
-}
-        """
-    }
-}
-
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpAuthenticationDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpAuthenticationDependencyResolutionIntegrationTest.groovy
deleted file mode 100644
index 76023f1..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpAuthenticationDependencyResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.http
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import org.gradle.test.fixtures.server.http.HttpServer
-import org.hamcrest.Matchers
-import spock.lang.Unroll
-
-class HttpAuthenticationDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-    static String badCredentials = "credentials{username 'testuser'; password 'bad'}"
-
-    @Unroll
-    public void "can resolve dependencies from #authScheme authenticated HTTP ivy repository"() {
-        server.start()
-        given:
-        def moduleA = ivyRepo().module('group', 'projectA', '1.2').publish()
-        ivyRepo().module('group', 'projectB', '2.1').publish()
-        ivyRepo().module('group', 'projectB', '2.2').publish()
-        def moduleB = ivyRepo().module('group', 'projectB', '2.3').publish()
-        ivyRepo().module('group', 'projectB', '3.0').publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy {
-        url "http://localhost:${server.port}/repo"
-
-        credentials {
-            password 'password'
-            username 'username'
-        }
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.2'
-    compile 'group:projectB:2.+'
-}
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar','projectB-2.3.jar']
-}
-"""
-
-        when:
-        server.authenticationScheme = authScheme
-
-        and:
-        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', 'username', 'password', moduleA.ivyFile)
-        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', 'username', 'password', moduleA.jarFile)
-        server.expectGetDirectoryListing("/repo/group/projectB/", 'username', 'password', moduleB.moduleDir.parentFile)
-        server.expectGet("/repo/group/projectB/2.3/ivy-2.3.xml", 'username', 'password', moduleB.ivyFile)
-        server.expectGet("/repo/group/projectB/2.3/projectB-2.3.jar", 'username', 'password', moduleB.jarFile)
-
-        then:
-        succeeds('listJars')
-
-        where:
-        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
-    }
-
-    @Unroll
-    public void "can resolve dependencies from #authScheme authenticated HTTP maven repository"() {
-        server.start()
-        given:
-        def moduleA = mavenRepo().module('group', 'projectA', '1.2').publish()
-        mavenRepo().module('group', 'projectB', '2.0').publish()
-        mavenRepo().module('group', 'projectB', '2.2').publish()
-        def moduleB = mavenRepo().module('group', 'projectB', '2.3').publish()
-        mavenRepo().module('group', 'projectB', '3.0').publish()
-        def moduleC = mavenRepo().module('group', 'projectC', '3.1-SNAPSHOT').publish()
-        def moduleD = mavenRepo().module('group', 'projectD', '4-SNAPSHOT').withNonUniqueSnapshots().publish()
-        and:
-        buildFile << """
-repositories {
-    maven {
-        url "http://localhost:${server.port}/repo"
-
-        credentials {
-            password 'password'
-            username 'username'
-        }
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.2'
-    compile 'group:projectB:2.+'
-    compile 'group:projectC:3.1-SNAPSHOT'
-    compile 'group:projectD:4-SNAPSHOT'
-}
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar', 'projectB-2.3.jar', 'projectC-3.1-SNAPSHOT.jar', 'projectD-4-SNAPSHOT.jar']
-}
-"""
-
-        when:
-        server.authenticationScheme = authScheme
-
-        and:
-        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.pom', 'username', 'password', moduleA.pomFile)
-        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', 'username', 'password', moduleA.artifactFile)
-        server.expectGet('/repo/group/projectB/maven-metadata.xml', 'username', 'password', moduleB.rootMetaDataFile)
-        server.expectGet('/repo/group/projectB/2.3/projectB-2.3.pom', 'username', 'password', moduleB.pomFile)
-        server.expectGet('/repo/group/projectB/2.3/projectB-2.3.jar', 'username', 'password', moduleB.artifactFile)
-
-        server.expectGet('/repo/group/projectC/3.1-SNAPSHOT/maven-metadata.xml', 'username', 'password', moduleC.metaDataFile)
-        server.expectGet('/repo/group/projectC/3.1-SNAPSHOT/maven-metadata.xml', 'username', 'password', moduleC.metaDataFile)
-        server.expectGet("/repo/group/projectC/3.1-SNAPSHOT/projectC-${moduleC.getPublishArtifactVersion()}.pom", 'username', 'password', moduleC.pomFile)
-        server.expectGet("/repo/group/projectC/3.1-SNAPSHOT/projectC-${moduleC.getPublishArtifactVersion()}.jar", 'username', 'password', moduleC.artifactFile)
-
-        server.expectGet('/repo/group/projectD/4-SNAPSHOT/maven-metadata.xml', 'username', 'password', moduleD.metaDataFile)
-        server.expectGet('/repo/group/projectD/4-SNAPSHOT/maven-metadata.xml', 'username', 'password', moduleD.metaDataFile)
-        server.expectGet("/repo/group/projectD/4-SNAPSHOT/projectD-4-SNAPSHOT.pom", 'username', 'password', moduleD.pomFile)
-        server.expectGet("/repo/group/projectD/4-SNAPSHOT/projectD-4-SNAPSHOT.jar", 'username', 'password', moduleD.artifactFile)
-
-        then:
-        succeeds('listJars')
-
-        where:
-        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
-    }
-
-    @Unroll
-    def "reports failure resolving with #credsName credentials from #authScheme authenticated HTTP ivy repository"() {
-        server.start()
-        given:
-        def module = ivyRepo().module('group', 'projectA', '1.2').publish()
-        when:
-        settingsFile << 'rootProject.name = "publish"'
-        buildFile << """
-   repositories {
-       ivy {
-            url "http://localhost:${server.port}/repo"
-            $creds
-        }
-   }
-   configurations { compile }
-   dependencies {
-       compile 'group:projectA:1.2'
-   }
-   task listJars << {
-       assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-   }
-   """
-
-        and:
-        server.authenticationScheme = authScheme
-        server.allowGetOrHead('/repo/group/projectA/1.2/ivy-1.2.xml', 'username', 'password', module.ivyFile)
-
-        then:
-        fails 'listJars'
-
-        and:
-        failure.assertHasDescription('Execution failed for task \':listJars\'.')
-        failure.assertHasCause('Could not resolve all dependencies for configuration \':compile\'.')
-        failure.assertThatCause(Matchers.containsString('Received status code 401 from server: Unauthorized'))
-
-        where:
-        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST, HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
-        credsName << ['empty', 'empty', 'bad', 'bad']
-        creds << ['', '', badCredentials, badCredentials]
-    }
-
-    @Unroll
-    def "reports failure resolving with #credsName credentials from #authScheme authenticated HTTP maven repository"() {
-        given:
-        server.start()
-
-        and:
-        def module = mavenRepo().module('group', 'projectA', '1.2').publish()
-
-        when:
-        settingsFile << 'rootProject.name = "publish"'
-        buildFile << """
- repositories {
-     maven {
-         url "http://localhost:${server.port}/repo"
-        $creds
-     }
- }
- configurations { compile }
- dependencies {
-     compile 'group:projectA:1.2'
- }
- task listJars << {
-     assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
- }
- """
-
-        and:
-        server.authenticationScheme = authScheme
-        server.allowGetOrHead('/repo/group/projectA/1.2/projectA-1.2.pom', 'username', 'password', module.pomFile)
-
-        then:
-        fails 'listJars'
-
-        and:
-        failure.assertHasDescription('Execution failed for task \':listJars\'.')
-        failure.assertHasCause('Could not resolve all dependencies for configuration \':compile\'.')
-        failure.assertThatCause(Matchers.containsString('Received status code 401 from server: Unauthorized'))
-
-        where:
-        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST, HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
-        credsName << ['empty', 'empty', 'bad', 'bad']
-        creds << ['', '', badCredentials, badCredentials]
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpEncodingDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpEncodingDependencyResolutionIntegrationTest.groovy
deleted file mode 100644
index 725f6f4..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpEncodingDependencyResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.http
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest;
-
-
-public class HttpEncodingDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-    public void "handles gzip encoded content"() {
-        server.start()
-
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.2')
-        module.publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.2' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-
-        when:
-        server.expectGetGZipped('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
-        server.expectGetGZipped('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
-
-        then:
-        succeeds('listJars')
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpProxyResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpProxyResolveIntegrationTest.groovy
deleted file mode 100644
index be4bc28..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpProxyResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.http
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import org.gradle.test.fixtures.server.http.HttpServer
-import org.gradle.test.fixtures.server.http.TestProxyServer
-import org.gradle.util.SetSystemProperties
-import org.junit.Rule
-import spock.lang.Unroll
-
-class HttpProxyResolveIntegrationTest extends AbstractDependencyResolutionTest {
-    @Rule TestProxyServer proxyServer = new TestProxyServer(server)
-    @Rule SetSystemProperties systemProperties = new SetSystemProperties()
-
-    public void "uses configured proxy to access remote HTTP repository"() {
-        server.start()
-        proxyServer.start()
-
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.2')
-        module.publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy { url "http://not.a.real.domain/repo" }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.2' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-
-        when:
-        executer.withArguments("-Dhttp.proxyHost=localhost", "-Dhttp.proxyPort=${proxyServer.port}")
-
-        and:
-        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
-
-        then:
-        succeeds('listJars')
-
-        and:
-        proxyServer.requestCount == 2
-    }
-
-    public void "uses authenticated proxy to access remote HTTP repository"() {
-        server.start()
-        proxyServer.start()
-
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.2')
-        module.publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy {
-        url "http://not.a.real.domain/repo"
-    }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.2' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-
-        when:
-        executer.withArguments("-Dhttp.proxyHost=localhost", "-Dhttp.proxyPort=${proxyServer.port}", "-Dhttp.nonProxyHosts=foo",
-                               "-Dhttp.proxyUser=proxyUser", "-Dhttp.proxyPassword=proxyPassword")
-
-        and:
-        proxyServer.requireAuthentication('proxyUser', 'proxyPassword')
-
-        and:
-        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
-
-        then:
-        succeeds('listJars')
-
-        and:
-        proxyServer.requestCount == 2
-    }
-
-    @Unroll
-    public void "passes target credentials to #authScheme authenticated server via proxy"() {
-        server.start()
-        proxyServer.start()
-
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.2')
-        module.publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy {
-        url "http://not.a.real.domain/repo"
-        credentials {
-            username 'targetUser'
-            password 'targetPassword'
-        }
-    }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.2' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-
-        when:
-        server.authenticationScheme = authScheme
-        executer.withArguments("-Dhttp.proxyHost=localhost", "-Dhttp.proxyPort=${proxyServer.port}", "-Dhttp.proxyUser=proxyUser", "-Dhttp.proxyPassword=proxyPassword")
-
-        and:
-        proxyServer.requireAuthentication('proxyUser', 'proxyPassword')
-
-        and:
-        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', 'targetUser', 'targetPassword', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', 'targetUser', 'targetPassword', module.jarFile)
-
-        then:
-        succeeds('listJars')
-
-        and:
-        // 1 extra request for authentication
-        proxyServer.requestCount == 3
-
-        where:
-        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpRedirectResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpRedirectResolveIntegrationTest.groovy
deleted file mode 100644
index f4f17d9..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpRedirectResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.http
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import org.gradle.test.fixtures.server.http.HttpServer
-import org.gradle.util.SetSystemProperties
-import org.junit.Rule
-import spock.lang.Issue
-
-class HttpRedirectResolveIntegrationTest extends AbstractDependencyResolutionTest {
-    @Rule SetSystemProperties systemProperties = new SetSystemProperties()
-    @Rule public final HttpServer server2 = new HttpServer()
-
-    public void "resolves module artifacts via HTTP redirect"() {
-        server.start()
-        server2.start()
-
-        given:
-        def module = ivyRepo().module('group', 'projectA').publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.0' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
-}
-"""
-
-        when:
-        server.expectGetRedirected('/repo/group/projectA/1.0/ivy-1.0.xml', "http://localhost:${server2.port}/redirected/group/projectA/1.0/ivy-1.0.xml")
-        server2.expectGet('/redirected/group/projectA/1.0/ivy-1.0.xml', module.ivyFile)
-        server.expectGetRedirected('/repo/group/projectA/1.0/projectA-1.0.jar', "http://localhost:${server2.port}/redirected/group/projectA/1.0/projectA-1.0.jar")
-        server2.expectGet('/redirected/group/projectA/1.0/projectA-1.0.jar', module.jarFile)
-
-        then:
-        succeeds('listJars')
-    }
-
-    @Issue('GRADLE-2196')
-    public void "resolves artifact-only module via HTTP redirect"() {
-        server.start()
-        server2.start()
-
-        given:
-        def module = ivyRepo().module('group', 'projectA').publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.0 at zip' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.0.zip']
-}
-"""
-
-        when:
-        server.expectGetMissing('/repo/group/projectA/1.0/ivy-1.0.xml')
-        server.expectHeadRedirected('/repo/group/projectA/1.0/projectA-1.0.zip', "http://localhost:${server2.port}/redirected/group/projectA/1.0/projectA-1.0.zip")
-        server2.expectHead('/redirected/group/projectA/1.0/projectA-1.0.zip', module.jarFile)
-        server.expectGetRedirected('/repo/group/projectA/1.0/projectA-1.0.zip', "http://localhost:${server2.port}/redirected/group/projectA/1.0/projectA-1.0.zip")
-        server2.expectGet('/redirected/group/projectA/1.0/projectA-1.0.zip', module.jarFile)
-
-        then:
-        succeeds('listJars')
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy
deleted file mode 100644
index 3114ef9..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.ivy
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import org.gradle.test.fixtures.ivy.IvyFileModule
-
-import static org.hamcrest.Matchers.containsString
-import static org.hamcrest.Matchers.startsWith
-
-class IvyBrokenRemoteResolveIntegrationTest extends AbstractDependencyResolutionTest {
-
-    public void "reports and caches missing module"() {
-        server.start()
-
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.2')
-        module.publish()
-
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo1"}
-    ivy { url "http://localhost:${server.port}/repo2"}
-}
-configurations { missing }
-dependencies {
-    missing 'group:projectA:1.2'
-}
-if (project.hasProperty('doNotCacheChangingModules')) {
-    configurations.all {
-        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-    }
-}
-task showMissing << { println configurations.missing.files }
-"""
-
-        when:
-        expectMissingArtifact("repo1", module)
-        expectExistingArtifact("repo2", module)
-
-        then:
-        succeeds("showMissing")
-
-        when:
-        server.resetExpectations() // Missing status in repo1 is cached
-        then:
-        succeeds('showMissing')
-    }
-
-    public void "reports and recovers from broken module"() {
-        server.start()
-
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.3')
-        module.publish()
-
-        buildFile << """
-repositories {
-    ivy {
-        url "http://localhost:${server.port}"
-    }
-}
-configurations { broken }
-dependencies {
-    broken 'group:projectA:1.3'
-}
-task showBroken << { println configurations.broken.files }
-"""
-
-        when:
-        server.addBroken('/')
-        fails("showBroken")
-
-        then:
-        failure.assertHasDescription('Execution failed for task \':showBroken\'.')
-        failure.assertHasCause('Could not resolve all dependencies for configuration \':broken\'.')
-        failure.assertHasCause('Could not resolve group:group, module:projectA, version:1.3')
-        failure.assertHasCause("Could not GET 'http://localhost:${server.port}/group/projectA/1.3/ivy-1.3.xml'. Received status code 500 from server: broken")
-
-        when:
-        server.resetExpectations()
-        server.expectGet('/group/projectA/1.3/ivy-1.3.xml', module.ivyFile)
-        server.expectGet('/group/projectA/1.3/projectA-1.3.jar', module.jarFile)
-
-        then:
-        succeeds("showBroken")
-    }
-
-    public void "reports and caches missing artifacts"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy {
-        url "http://localhost:${server.port}"
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.2'
-}
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        and:
-        def module = ivyRepo().module('group', 'projectA', '1.2')
-        module.publish()
-
-        when:
-        server.expectGet('/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
-        server.expectGetMissing('/group/projectA/1.2/projectA-1.2.jar')
-
-        then:
-        fails "retrieve"
-
-        failure.assertThatCause(containsString("Artifact 'group:projectA:1.2 at jar' not found"))
-
-        when:
-        server.resetExpectations()
-
-        then:
-        fails "retrieve"
-        failure.assertThatCause(containsString("Artifact 'group:projectA:1.2 at jar' not found"))
-    }
-
-    public void "reports and recovers from failed artifact download"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy {
-        url "http://localhost:${server.port}"
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.2'
-}
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        and:
-        def module = ivyRepo().module('group', 'projectA', '1.2')
-        module.publish()
-
-        when:
-        server.expectGet('/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
-        server.addBroken('/group/projectA/1.2/projectA-1.2.jar')
-
-        then:
-        fails "retrieve"
-        failure.assertHasCause("Could not download artifact 'group:projectA:1.2 at jar'")
-        failure.assertThatCause(startsWith("Could not GET"))
-
-        when:
-        server.resetExpectations()
-        server.expectGet('/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
-
-        then:
-        succeeds "retrieve"
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-    }
-
-    def expectExistingArtifact(String repo, IvyFileModule module) {
-        server.expectGet("/${repo}/${module.getOrganisation()}/${module.getModule()}/${module.getRevision()}/ivy-${module.getRevision()}.xml", module.ivyFile)
-        server.expectGet("/${repo}/${module.getOrganisation()}/${module.getModule()}/${module.getRevision()}/${module.getModule()}-${module.getRevision()}.jar", module.jarFile)
-    }
-
-    def expectMissingArtifact(String repo, IvyFileModule module) {
-        server.expectGetMissing("/${repo}/${module.getOrganisation()}/${module.getModule()}/${module.getRevision()}/ivy-${module.getRevision()}.xml")
-        server.expectHeadMissing("/${repo}/${module.getOrganisation()}/${module.getModule()}/${module.getRevision()}/${module.getModule()}-${module.getRevision()}.jar")
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolveIntegrationTest.groovy
deleted file mode 100644
index 9be2087..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,425 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.ivy
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class IvyChangingModuleRemoteResolveIntegrationTest extends AbstractDependencyResolutionTest {
-
-    def "detects changed module descriptor when flagged as changing"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy { url "${ivyHttpRepo.uri}" }
-}
-
-configurations { compile }
-
-configurations.all {
-    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-}
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.1", changing: true
-}
-
-task retrieve(type: Copy) {
-    into 'build'
-    from configurations.compile
-}
-"""
-
-        when: "Version 1.1 is published"
-        def module = ivyHttpRepo.module("group", "projectA", "1.1").publish()
-
-        and: "Server handles requests"
-        module.expectIvyGet()
-        module.expectJarGet()
-
-        and: "We request 1.1 (changing)"
-        run 'retrieve'
-
-        then: "Version 1.1 jar is downloaded"
-        file('build').assertHasDescendants('projectA-1.1.jar')
-
-        when: "Module meta-data is changed (new artifact)"
-        module.artifact([name: 'other'])
-        module.dependsOn("group", "projectB", "2.0")
-        module.publish()
-        def moduleB = ivyHttpRepo.module("group", "projectB", "2.0").publish()
-
-        and: "Server handles requests"
-        server.resetExpectations()
-        // Server will be hit to get updated versions
-        module.expectIvyHead()
-        module.expectIvySha1Get()
-        module.expectIvyGet()
-        module.expectJarHead()
-        module.expectJarSha1Get()
-        module.expectArtifactGet('other')
-        moduleB.expectIvyGet()
-        moduleB.expectJarGet()
-
-        and: "We request 1.1 again"
-        run 'retrieve'
-
-        then: "We get all artifacts, including the new ones"
-        file('build').assertHasDescendants('projectA-1.1.jar', 'other-1.1.jar', 'projectB-2.0.jar')
-    }
-
-    def "can mark a module as changing after first retrieval"() {
-        server.start()
-
-        given:
-        buildFile << """
-def isChanging = project.hasProperty('isChanging') ? true : false
-repositories {
-    ivy { url "${ivyHttpRepo.uri}" }
-}
-
-configurations { compile }
-configurations.compile.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.1", changing: isChanging
-}
-
-task retrieve(type: Copy) {
-    into 'build'
-    from configurations.compile
-}
-"""
-        and:
-        def module = ivyHttpRepo.module("group", "projectA", "1.1").publish()
-        module.allowAll()
-
-        when: 'original retrieve'
-        run 'retrieve'
-
-        then:
-        def jarSnapshot = file('build/projectA-1.1.jar').snapshot()
-
-        when:
-        server.resetExpectations()
-        module.publishWithChangedContent()
-        module.expectIvyHead()
-        module.expectIvySha1Get()
-        module.expectIvyGet()
-        module.expectJarHead()
-        module.expectJarSha1Get()
-        module.expectJarGet()
-
-        and:
-        executer.withArguments('-PisChanging')
-        run 'retrieve'
-
-        then:
-        file('build/projectA-1.1.jar').assertHasChangedSince(jarSnapshot)
-    }
-
-    def "detects changed artifact when flagged as changing"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy { url "${ivyHttpRepo.uri}" }
-}
-
-configurations { compile }
-
-configurations.all {
-    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-}
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.1", changing: true
-}
-
-task retrieve(type: Copy) {
-    into 'build'
-    from configurations.compile
-}
-"""
-
-        and:
-        def module = ivyHttpRepo.module("group", "projectA", "1.1").publish()
-
-        when:
-        module.expectIvyGet()
-        module.expectJarGet()
-
-        run 'retrieve'
-
-        then:
-        def jarFile = file('build/projectA-1.1.jar')
-        jarFile.assertIsCopyOf(module.jarFile)
-        def snapshot = jarFile.snapshot()
-
-        when:
-        module.publishWithChangedContent()
-
-        server.resetExpectations()
-        // Server will be hit to get updated versions
-        module.expectIvyHead()
-        module.expectIvySha1Get()
-        module.expectIvyGet()
-        module.expectJarHead()
-        module.expectJarSha1Get()
-        module.expectJarGet()
-
-        run 'retrieve'
-
-        then:
-        def changedJarFile = file('build/projectA-1.1.jar')
-        changedJarFile.assertHasChangedSince(snapshot)
-        changedJarFile.assertIsCopyOf(module.jarFile)
-    }
-
-    def "caches changing module descriptor and artifacts until cache expiry"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy { url "${ivyHttpRepo.uri}" }
-}
-
-configurations { compile }
-
-
-if (project.hasProperty('doNotCacheChangingModules')) {
-    configurations.all {
-        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-    }
-}
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.1", changing: true
-}
-
-task retrieve(type: Copy) {
-    into 'build'
-    from configurations.compile
-}
-"""
-
-        when: "Version 1.1 is published"
-        def module = ivyHttpRepo.module("group", "projectA", "1.1").publish()
-
-        and: "Server handles requests"
-        module.expectIvyGet()
-        module.expectJarGet()
-
-        and: "We request 1.1 (changing)"
-        run 'retrieve'
-
-        then: "Version 1.1 jar is downloaded"
-        file('build').assertHasDescendants('projectA-1.1.jar')
-        def jarFile = file('build/projectA-1.1.jar')
-        jarFile.assertIsCopyOf(module.jarFile)
-        def snapshot = jarFile.snapshot()
-
-        when: "Module meta-data is changed and artifacts are modified"
-        server.resetExpectations()
-        module.artifact([name: 'other'])
-        module.publishWithChangedContent()
-
-        and: "We request 1.1 (changing), with module meta-data cached. No server requests."
-        run 'retrieve'
-
-        then: "Original module meta-data and artifacts are used"
-        file('build').assertHasDescendants('projectA-1.1.jar')
-        jarFile.assertHasNotChangedSince(snapshot)
-
-        when: "Server handles requests"
-        server.resetExpectations()
-        // Server will be hit to get updated versions
-        module.expectIvyHead()
-        module.expectIvySha1Get()
-        module.expectIvyGet()
-        module.expectJarHead()
-        module.expectJarSha1Get()
-        module.expectJarGet()
-        module.expectArtifactGet('other')
-
-        and: "We request 1.1 (changing) again, with zero expiry for dynamic revision cache"
-        executer.withArguments("-PdoNotCacheChangingModules")
-        run 'retrieve'
-
-        then: "We get new artifacts based on the new meta-data"
-        file('build').assertHasDescendants('projectA-1.1.jar', 'other-1.1.jar')
-        jarFile.assertHasChangedSince(snapshot)
-        jarFile.assertIsCopyOf(module.jarFile)
-    }
-
-    def "can use cache-control DSL to mimic changing pattern for ivy repository"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy { url "${ivyHttpRepo.uri}" }
-}
-
-configurations { compile }
-
-import static java.util.concurrent.TimeUnit.SECONDS
-configurations.all {
-    resolutionStrategy.resolutionRules.with {
-        eachModule({ moduleResolve ->
-            if (moduleResolve.request.version.endsWith('-CHANGING')) {
-                moduleResolve.cacheFor(0, SECONDS)
-            }
-        } as Action)
-
-        eachArtifact({ artifactResolve ->
-            if (artifactResolve.request.moduleVersionIdentifier.version.endsWith('-CHANGING')) {
-                artifactResolve.cacheFor(0, SECONDS)
-            }
-        } as Action)
-    }
-}
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1-CHANGING"
-}
-
-task retrieve(type: Copy) {
-    into 'build'
-    from configurations.compile
-}
-"""
-
-        when: "Version 1-CHANGING is published"
-        def module = ivyHttpRepo.module("group", "projectA", "1-CHANGING").publish()
-
-        and: "Server handles requests"
-        module.expectIvyGet()
-        module.expectJarGet()
-
-        and: "We request 1-CHANGING"
-        run 'retrieve'
-
-        then: "Version 1-CHANGING jar is used"
-        file('build').assertHasDescendants('projectA-1-CHANGING.jar')
-        def jarFile = file('build/projectA-1-CHANGING.jar')
-        jarFile.assertIsCopyOf(module.jarFile)
-        def snapshot = jarFile.snapshot()
-
-        when: "Module meta-data is changed and artifacts are modified"
-        module.artifact([name: 'other'])
-        module.publishWithChangedContent()
-
-        and: "Server handles requests"
-        server.resetExpectations()
-        // Server will be hit to get updated versions
-        module.expectIvyHead()
-        module.expectIvySha1Get()
-        module.expectIvyGet()
-        module.expectJarHead()
-        module.expectJarSha1Get()
-        module.expectJarGet()
-        module.expectArtifactGet('other')
-
-        and: "We request 1-CHANGING again"
-        executer.withArguments()
-        run 'retrieve'
-
-        then: "We get new artifacts based on the new meta-data"
-        file('build').assertHasDescendants('projectA-1-CHANGING.jar', 'other-1-CHANGING.jar')
-        jarFile.assertHasChangedSince(snapshot)
-        jarFile.assertIsCopyOf(module.jarFile)
-    }
-
-    def "avoid redownload unchanged artifact when no checksum available"() {
-        server.start()
-
-        given:
-        buildFile << """
-            repositories {
-                ivy { url "${ivyHttpRepo.uri}" }
-            }
-
-            configurations { compile }
-
-            configurations.all {
-                resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-            }
-
-            dependencies {
-                compile group: "group", name: "projectA", version: "1.1", changing: true
-            }
-
-            task retrieve(type: Copy) {
-                into 'build'
-                from configurations.compile
-            }
-        """
-
-        and:
-        def module = ivyHttpRepo.module("group", "projectA", "1.1").publish()
-        
-        def base = "/repo/group/projectA/1.1"
-        def ivyPath = "$base/$module.ivyFile.name"
-        def ivySha1Path = "${ivyPath}.sha1"
-        def jarPath = "$base/$module.jarFile.name"
-        def jarSha1Path = "${jarPath}.sha1"
-
-        when:
-        module.expectIvyGet()
-        module.expectJarGet()
-
-        and:
-        run 'retrieve'
-
-        then:
-        def downloadedJar = file('build/projectA-1.1.jar')
-        downloadedJar.assertIsCopyOf(module.jarFile)
-        def snapshot = downloadedJar.snapshot()
-
-        when:
-        server.resetExpectations()
-        module.expectIvyHead()
-        module.expectJarHead()
-
-        and:
-        run 'retrieve'
-
-        then:
-        downloadedJar.assertHasNotChangedSince(snapshot)
-
-        when:
-        // Do change the jar, so we can check that the new version wasn't downloaded
-        module.publishWithChangedContent()
-
-        server.resetExpectations()
-        module.expectIvyHead()
-        module.expectIvySha1GetMissing()
-        module.expectIvyGet()
-        module.expectJarHead()
-        module.expectJarSha1GetMissing()
-        module.expectJarGet()
-
-        run 'retrieve'
-
-        then:
-        downloadedJar.assertHasChangedSince(snapshot)
-        downloadedJar.assertIsCopyOf(module.jarFile)
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy
deleted file mode 100644
index 92f6fcd..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,739 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.ivy
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class IvyDynamicRevisionRemoteResolveIntegrationTest extends AbstractDependencyResolutionTest {
-
-    def "uses latest version from version range and latest status"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy {
-        url "${ivyHttpRepo.uri}"
-    }
-}
-
-configurations { compile }
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.+"
-    compile group: "group", name: "projectB", version: "latest.integration"
-}
-
-configurations.all {
-    resolutionStrategy.cacheDynamicVersionsFor 0, "seconds"
-}
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        when: "Version 1.1 is published"
-        def projectA1 = ivyHttpRepo.module("group", "projectA", "1.1").publish()
-        ivyHttpRepo.module("group", "projectA", "2.0").publish()
-        def projectB1 = ivyHttpRepo.module("group", "projectB", "1.1").publish()
-
-        and: "Server handles requests"
-        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        projectA1.expectIvyGet()
-        projectA1.expectJarGet()
-        ivyHttpRepo.expectDirectoryListGet("group", "projectB")
-        projectB1.expectIvyGet()
-        projectB1.expectJarGet()
-
-        and:
-        run 'retrieve'
-
-        then: "Version 1.1 is used"
-        file('libs').assertHasDescendants('projectA-1.1.jar', 'projectB-1.1.jar')
-        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
-        file('libs/projectB-1.1.jar').assertIsCopyOf(projectB1.jarFile)
-
-        when: "New versions are published"
-        def projectA2 = ivyHttpRepo.module("group", "projectA", "1.2").publish()
-        def projectB2 = ivyHttpRepo.module("group", "projectB", "2.2").publish()
-
-        and: "Server handles requests"
-        server.resetExpectations()
-        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        projectA2.expectIvyGet()
-        projectA2.expectJarGet()
-        ivyHttpRepo.expectDirectoryListGet("group", "projectB")
-        projectB2.expectIvyGet()
-        projectB2.expectJarGet()
-
-        and:
-        run 'retrieve'
-
-        then: "New versions are used"
-        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-2.2.jar')
-        file('libs/projectA-1.2.jar').assertIsCopyOf(projectA2.jarFile)
-        file('libs/projectB-2.2.jar').assertIsCopyOf(projectB2.jarFile)
-    }
-
-    def "determines latest version with jar only"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-  ivy {
-      url "${ivyHttpRepo.uri}"
-  }
-}
-
-configurations { compile }
-
-dependencies {
-  compile group: "group", name: "projectA", version: "1.+"
-}
-
-task retrieve(type: Sync) {
-  from configurations.compile
-  into 'libs'
-}
-"""
-
-        when: "Version 1.1 is published"
-        def projectA11 = ivyHttpRepo.module("group", "projectA", "1.1").withNoMetaData().publish()
-        def projectA12 = ivyHttpRepo.module("group", "projectA", "1.2").withNoMetaData().publish()
-        ivyHttpRepo.module("group", "projectA", "2.0").withNoMetaData().publish()
-
-        and: "Server handles requests"
-        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        projectA12.expectIvyGetMissing()
-        projectA11.expectIvyGetMissing()
-
-        // TODO - Should not list twice
-        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        projectA12.expectJarHead()
-        projectA12.expectJarGet()
-
-        and:
-        run 'retrieve'
-
-        then: "Version 1.2 is used"
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-    }
-
-    def "uses latest version with correct status for latest.release and latest.milestone"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy {
-        url "${ivyHttpRepo.uri}"
-    }
-}
-
-configurations {
-    release
-    milestone
-}
-
-dependencies {
-    release group: "group", name: "projectA", version: "latest.release"
-    milestone group: "group", name: "projectA", version: "latest.milestone"
-}
-
-task retrieveRelease(type: Sync) {
-    from configurations.release
-    into 'release'
-}
-
-task retrieveMilestone(type: Sync) {
-    from configurations.milestone
-    into 'milestone'
-}
-"""
-
-        when: "Versions are published"
-        ivyHttpRepo.module("group", "projectA", "1.0").withStatus('release').publish()
-        ivyHttpRepo.module('group', 'projectA', '1.1').withStatus('milestone').publish()
-        ivyHttpRepo.module('group', 'projectA', '1.2').withStatus('integration').publish()
-        def release = ivyHttpRepo.module("group", "projectA", "2.0").withStatus('release').publish()
-        def milestone = ivyHttpRepo.module('group', 'projectA', '2.1').withStatus('milestone').publish()
-        def integration = ivyHttpRepo.module('group', 'projectA', '2.2').withStatus('integration').publish()
-
-        and: "Server handles requests"
-        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        integration.expectIvyGet()
-        milestone.expectIvyGet()
-        release.expectIvyGet()
-        release.expectJarGet()
-
-        and:
-        run 'retrieveRelease'
-
-        then:
-        file('release').assertHasDescendants('projectA-2.0.jar')
-
-        when:
-        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        integration.expectIvyHead()
-        milestone.expectIvyHead()
-        milestone.expectJarGet()
-
-        and:
-        run 'retrieveMilestone'
-
-        then:
-        file('milestone').assertHasDescendants('projectA-2.1.jar')
-    }
-
-    def "can use latest version from different remote repositories"() {
-        server.start()
-        def repo1 = ivyHttpRepo("ivy1")
-        def repo2 = ivyHttpRepo("ivy2")
-
-        given:
-        buildFile << """
-    repositories {
-        ivy {
-            url "${repo1.uri}"
-        }
-        ivy {
-            url "${repo2.uri}"
-        }
-    }
-
-    configurations {
-        milestone
-    }
-
-    dependencies {
-        milestone group: "group", name: "projectA", version: "latest.milestone"
-    }
-
-    task retrieveMilestone(type: Sync) {
-        from configurations.milestone
-        into 'milestone'
-    }
-    """
-
-        when: "Versions are published"
-        def version11 = repo1.module('group', 'projectA', '1.1').withStatus('milestone').publish()
-        def version12 = repo2.module('group', 'projectA', '1.2').withStatus('integration').publish()
-
-        and: "Server handles requests"
-        repo1.expectDirectoryListGet("group", "projectA")
-        version11.expectIvyGet()
-        version11.expectJarGet()
-        repo2.expectDirectoryListGet("group", "projectA")
-        version12.expectIvyGet()
-        // TODO - shouldn't need this
-        repo2.expectDirectoryListGet("group", "projectA")
-        // TODO - shouldn't need this
-        version12.expectJarGet()
-
-        and:
-        run 'retrieveMilestone'
-
-        then:
-        file('milestone').assertHasDescendants('projectA-1.1.jar')
-    }
-
-    def "checks new repositories before returning any cached value"() {
-        server.start()
-        def repo1 = ivyHttpRepo("repo1")
-        def repo2 = ivyHttpRepo("repo2")
-
-        given:
-        buildFile << """
-repositories {
-    ivy { url "${repo1.uri}" }
-}
-
-if (project.hasProperty('addRepo2')) {
-    repositories {
-        ivy { url "${repo2.uri}" }
-    }
-}
-
-configurations { compile }
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.+"
-}
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        when:
-        def projectA11 = repo1.module("group", "projectA", "1.1").publish()
-        def projectA12 = repo2.module("group", "projectA", "1.2").publish()
-
-        and: "Server handles requests"
-        repo1.expectDirectoryListGet("group", "projectA")
-        projectA11.expectIvyGet()
-        projectA11.expectJarGet()
-
-        and: "Retrieve with only repo1"
-        run 'retrieve'
-
-        then: "Version 1.1 is used"
-        file('libs').assertHasDescendants('projectA-1.1.jar')
-
-        when: "Server handles requests"
-        server.resetExpectations()
-        repo2.expectDirectoryListGet("group", "projectA")
-        projectA12.expectIvyGet()
-        projectA12.expectJarGet()
-
-        and: "Retrieve with both repos"
-        executer.withArguments("-PaddRepo2")
-        run 'retrieve'
-
-        then: "Version 1.2 is used"
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-    }
-
-    def "does not cache information about broken modules"() {
-        server.start()
-        def repo1 = ivyHttpRepo("repo1")
-        def repo2 = ivyHttpRepo("repo2")
-
-        given:
-        buildFile << """
-    repositories {
-        ivy { url "${repo1.uri}" }
-        ivy { url "${repo2.uri}" }
-    }
-
-    configurations { compile }
-
-    dependencies {
-        compile group: "group", name: "projectA", version: "1.+"
-    }
-
-    task retrieve(type: Sync) {
-        from configurations.compile
-        into 'libs'
-    }
-    """
-
-        when:
-        def projectA12 = repo1.module("group", "projectA", "1.2").publish()
-        def projectA11 = repo2.module("group", "projectA", "1.1").publish()
-
-        and: "projectA is broken in repo1"
-        server.addBroken("/repo1/group/projectA/")
-        repo2.expectDirectoryListGet("group", "projectA")
-        projectA11.expectIvyGet()
-        projectA11.expectJarGet()
-
-        and: "Retrieve with only repo2"
-        run 'retrieve'
-
-        then: "Version 1.1 is used"
-        file('libs').assertHasDescendants('projectA-1.1.jar')
-
-        when: "Server handles requests"
-        server.resetExpectations()
-        repo1.expectDirectoryListGet("group", "projectA")
-        projectA12.expectIvyGet()
-        projectA12.expectJarGet()
-
-        and: "Retrieve with both repos"
-        run 'retrieve'
-
-        then: "Version 1.2 is used"
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-    }
-
-    def "uses and caches latest of versions obtained from multiple HTTP repositories"() {
-        server.start()
-        def repo1 = ivyHttpRepo("repo1")
-        def repo2 = ivyHttpRepo("repo2")
-        def repo3 = ivyHttpRepo("repo3")
-
-        given:
-        buildFile << """
-repositories {
-    ivy { url "${repo1.uri}" }
-    ivy { url "${repo2.uri}" }
-    ivy { url "${repo3.uri}" }
-}
-
-configurations { compile }
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.+"
-}
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        when: "Versions are published"
-        def projectA11 = repo1.module("group", "projectA", "1.1").publish()
-        def projectA12 = repo3.module("group", "projectA", "1.2").publish()
-
-        and: "Server handles requests"
-        repo1.expectDirectoryListGet("group", "projectA")
-        // TODO Should not need to get this
-        projectA11.expectIvyGet()
-        // TODO Should only list missing directory once
-        repo2.expectDirectoryListGet("group", "projectA")
-        repo2.expectDirectoryListGet("group", "projectA")
-        repo3.expectDirectoryListGet("group", "projectA")
-        projectA12.expectIvyGet()
-        projectA12.expectJarGet()
-
-        and:
-        run 'retrieve'
-
-        then: "Version 1.2 is used"
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-
-        when: "Run again with cached dependencies"
-        server.resetExpectations()
-        def result = run 'retrieve'
-
-        then: "No server requests, task skipped"
-        result.assertTaskSkipped(':retrieve')
-    }
-
-    def "reuses cached artifacts that match multiple dynamic versions"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy { url "${ivyHttpRepo.uri}" }
-}
-
-configurations { deps1; deps2 }
-
-dependencies {
-    deps1 group: "org.test", name: "projectA", version: "1.+"
-    deps2 group: "org.test", name: "projectA", version: "[1.0,2.0)"
-}
-
-task retrieve1(type: Sync) {
-    from configurations.deps1
-    into 'libs1'
-}
-task retrieve2(type: Sync) {
-    from configurations.deps2
-    into 'libs2'
-}
-"""
-
-        when:
-        ivyHttpRepo.module("org.test", "projectA", "1.1").publish()
-        def projectA12 = ivyHttpRepo.module("org.test", "projectA", "1.2").publish()
-
-        and:
-        ivyHttpRepo.expectDirectoryListGet("org.test", "projectA")
-        projectA12.expectIvyGet()
-        projectA12.expectJarGet()
-
-        and:
-        run 'retrieve1'
-
-        then:
-        file('libs1').assertHasDescendants('projectA-1.2.jar')
-
-        when:
-        server.resetExpectations()
-        ivyHttpRepo.expectDirectoryListGet("org.test", "projectA")
-        projectA12.expectIvyHead()
-
-        and:
-        run 'retrieve2'
-
-        then:
-        file('libs1').assertHasDescendants('projectA-1.2.jar')
-    }
-
-    def "caches resolved revisions until cache expiry"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy {
-        url "${ivyHttpRepo.uri}"
-    }
-}
-
-configurations { compile }
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.+"
-}
-
-if (project.hasProperty('noDynamicRevisionCache')) {
-    configurations.all {
-        resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
-    }
-}
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        when: "Version 1.1 is published"
-        def version1 = ivyHttpRepo.module("group", "projectA", "1.1").publish()
-
-        and: "Server handles requests"
-        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        version1.expectIvyGet()
-        version1.expectJarGet()
-
-        and: "We request 1.+"
-        run 'retrieve'
-
-        then: "Version 1.1 is used"
-        file('libs').assertHasDescendants('projectA-1.1.jar')
-        file('libs/projectA-1.1.jar').assertIsCopyOf(version1.jarFile)
-
-        when: "Version 1.2 is published"
-        def version2 = ivyHttpRepo.module("group", "projectA", "1.2").publish()
-
-        and: "We request 1.+, with dynamic mappings cached. No server requests."
-        run 'retrieve'
-
-        then: "Version 1.1 is still used, as the 1.+ -> 1.1 mapping is cached"
-        file('libs').assertHasDescendants('projectA-1.1.jar')
-        file('libs/projectA-1.1.jar').assertIsCopyOf(version1.jarFile)
-
-        when: "Server handles requests"
-        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        version2.expectIvyGet()
-        version2.expectJarGet()
-
-        and: "We request 1.+, with zero expiry for dynamic revision cache"
-        executer.withArguments("-PnoDynamicRevisionCache").withTasks('retrieve').run()
-
-        then: "Version 1.2 is used"
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-        file('libs/projectA-1.2.jar').assertIsCopyOf(version2.jarFile)
-    }
-
-    def "uses and caches dynamic revisions for transitive dependencies"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy {
-        url "${ivyHttpRepo.uri}"
-    }
-}
-
-configurations { compile }
-
-dependencies {
-    compile group: "group", name: "main", version: "1.0"
-}
-
-if (project.hasProperty('noDynamicRevisionCache')) {
-    configurations.all {
-        resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
-    }
-}
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        when: "Version is published"
-        def mainProject = ivyHttpRepo.module("group", "main", "1.0")
-        mainProject.dependsOn("group", "projectA", "1.+")
-        mainProject.dependsOn("group", "projectB", "latest.integration")
-        mainProject.publish()
-
-        and: "transitive dependencies have initial values"
-        def projectA1 = ivyHttpRepo.module("group", "projectA", "1.1").publish()
-        def projectB1 = ivyHttpRepo.module("group", "projectB", "1.1").publish()
-
-        and: "Server handles requests"
-        mainProject.expectIvyGet()
-        mainProject.expectJarGet()
-        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        projectA1.expectIvyGet()
-        projectA1.expectJarGet()
-        ivyHttpRepo.expectDirectoryListGet("group", "projectB")
-        projectB1.expectIvyGet()
-        projectB1.expectJarGet()
-
-        and:
-        run 'retrieve'
-
-        then: "Initial transitive dependencies are used"
-        file('libs').assertHasDescendants('main-1.0.jar', 'projectA-1.1.jar', 'projectB-1.1.jar')
-        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
-        file('libs/projectB-1.1.jar').assertIsCopyOf(projectB1.jarFile)
-
-        when: "New versions are published"
-        def projectA2 = ivyHttpRepo.module("group", "projectA", "1.2").publish()
-        def projectB2 = ivyHttpRepo.module("group", "projectB", "2.2").publish()
-
-        and: "No server requests"
-        server.resetExpectations()
-
-        and:
-        run 'retrieve'
-
-        then: "Cached versions are used"
-        file('libs').assertHasDescendants('main-1.0.jar', 'projectA-1.1.jar', 'projectB-1.1.jar')
-        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
-        file('libs/projectB-1.1.jar').assertIsCopyOf(projectB1.jarFile)
-
-        when: "Server handles requests"
-        server.resetExpectations()
-        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
-        projectA2.expectIvyGet()
-        projectA2.expectJarGet()
-        ivyHttpRepo.expectDirectoryListGet("group", "projectB")
-        projectB2.expectIvyGet()
-        projectB2.expectJarGet()
-
-        and: "DynamicRevisionCache is bypassed"
-        executer.withArguments("-PnoDynamicRevisionCache")
-        run 'retrieve'
-
-        then: "New versions are used"
-        file('libs').assertHasDescendants('main-1.0.jar', 'projectA-1.2.jar', 'projectB-2.2.jar')
-        file('libs/projectA-1.2.jar').assertIsCopyOf(projectA2.jarFile)
-        file('libs/projectB-2.2.jar').assertIsCopyOf(projectB2.jarFile)
-    }
-
-    public void "resolves dynamic version with 2 repositories where first repo results in 404 for directory listing"() {
-        server.start()
-        given:
-        def repo = ivyHttpRepo("repo2")
-        def moduleA = repo.module('group', 'projectA').publish()
-
-        and:
-        buildFile << """
-            repositories {
-                ivy { url "http://localhost:${server.port}/repo1" }
-                ivy { url "${repo.uri}" }
-            }
-            configurations { compile }
-            dependencies {
-                compile 'group:projectA:1.+'
-            }
-            task listJars << {
-                assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
-            }
-            """
-
-        when:
-        server.expectGetMissing('/repo1/group/projectA/')
-        // TODO - should only list versions once
-        server.expectGetMissing('/repo1/group/projectA/')
-        repo.expectDirectoryListGet("group", "projectA")
-        moduleA.expectIvyGet()
-        moduleA.expectJarGet()
-
-        then:
-        succeeds('listJars')
-
-        when:
-        server.resetExpectations()
-        // No extra calls for cached dependencies
-        then:
-        succeeds('listJars')
-    }
-
-    def "reuses cached artifacts across repository types"() {
-        server.start()
-        def ivyRepo = ivyHttpRepo('repo1')
-        def mavenRepo = mavenHttpRepo('repo2')
-        def ivyModule = ivyRepo.module("org.test", "a", "1.1").publish()
-        def mavenModule = mavenRepo.module("org.test", "a", "1.1").publish()
-        assert ivyModule.jarFile.bytes == mavenModule.artifactFile.bytes
-
-        given:
-        buildFile.text = """
-repositories {
-    ivy { url '${ivyRepo.uri}' }
-}
-
-configurations { compile }
-
-dependencies {
-    compile 'org.test:a:1+'
-}
-
-task retrieve(type: Sync) {
-    into 'build'
-    from configurations.compile
-}
-"""
-
-        when:
-        ivyRepo.expectDirectoryListGet("org.test", "a")
-        ivyModule.expectIvyGet()
-        ivyModule.expectJarGet()
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('build').assertHasDescendants('a-1.1.jar')
-
-        when:
-        buildFile.text = """
-repositories {
-    maven { url '${mavenRepo.uri}' }
-}
-
-configurations { compile }
-
-dependencies {
-    compile 'org.test:a:[1.0,2.0)'
-}
-
-task retrieve(type: Sync) {
-    into 'build'
-    from configurations.compile
-}
-"""
-
-        and:
-        mavenRepo.expectMetaDataGet("org.test", "a")
-        mavenModule.expectPomGet()
-        mavenModule.expectArtifactHead()
-        mavenModule.expectArtifactSha1Get()
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('build').assertHasDescendants('a-1.1.jar')
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionResolveIntegrationTest.groovy
deleted file mode 100644
index ab7c953..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.ivy
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import spock.lang.Ignore
-import spock.lang.Issue
-
-class IvyDynamicRevisionResolveIntegrationTest extends AbstractDependencyResolutionTest {
-    @Ignore
-    @Issue("GRADLE-2502")
-    public void "latest.integration selects highest version regardless of status"() {
-        given:
-        buildFile << """
-  repositories {
-      ivy {
-          url "${ivyRepo.uri}"
-      }
-  }
-  configurations { compile }
-  dependencies {
-      compile 'org.test:projectA:latest.integration'
-  }
-  task retrieve(type: Sync) {
-      from configurations.compile
-      into 'libs'
-  }
-  """
-
-        when:
-        runAndFail 'retrieve'
-
-        then:
-        failureHasCause 'Could not find any version that matches group:group, module:projectA, version:latest.integration.'
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '1.0').withNoMetaData().publish()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.0.jar')
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '1.1').withStatus('integration').publish()
-        ivyRepo.module('org.test', 'projectA', '1.2').withStatus('integration').publish()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '1.3').withStatus('release').publish()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.3.jar')
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '1.4').withNoMetaData().publish()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.4.jar')
-    }
-
-    @Issue("GRADLE-2502")
-    public void "latest.milestone selects highest version with milestone or release status"() {
-        given:
-        buildFile << """
-  repositories {
-      ivy {
-          url "${ivyRepo.uri}"
-      }
-  }
-  configurations { compile }
-  dependencies {
-      compile 'org.test:projectA:latest.milestone'
-  }
-  task retrieve(type: Sync) {
-      from configurations.compile
-      into 'libs'
-  }
-  """
-
-        when:
-        runAndFail 'retrieve'
-
-        then:
-        failureHasCause 'Could not find any version that matches group:org.test, module:projectA, version:latest.milestone.'
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '2.0').withNoMetaData().publish()
-        runAndFail 'retrieve'
-
-        then:
-        failureHasCause 'Could not find any version that matches group:org.test, module:projectA, version:latest.milestone.'
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '1.3').withStatus('integration').publish()
-        runAndFail 'retrieve'
-
-        then:
-        failureHasCause 'Could not find any version that matches group:org.test, module:projectA, version:latest.milestone.'
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '1.0').withStatus('milestone').publish()
-        ivyRepo.module('org.test', 'projectA', '1.1').withStatus('milestone').publish()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.1.jar')
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '1.2').withStatus('release').publish()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-    }
-
-    @Issue("GRADLE-2502")
-    public void "latest.release selects highest version with release status"() {
-        given:
-        buildFile << """
-  repositories {
-      ivy {
-          url "${ivyRepo.uri}"
-      }
-  }
-  configurations { compile }
-  dependencies {
-      compile 'org.test:projectA:latest.release'
-  }
-  task retrieve(type: Sync) {
-      from configurations.compile
-      into 'libs'
-  }
-  """
-
-        when:
-        runAndFail 'retrieve'
-
-        then:
-        failureHasCause 'Could not find any version that matches group:org.test, module:projectA, version:latest.release.'
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '2.0').withNoMetaData().publish()
-        runAndFail 'retrieve'
-
-        then:
-        failureHasCause 'Could not find any version that matches group:org.test, module:projectA, version:latest.release.'
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '1.3').withStatus('integration').publish()
-        ivyRepo.module('org.test', 'projectA', '1.2').withStatus('milestone').publish()
-        runAndFail 'retrieve'
-
-        then:
-        failureHasCause 'Could not find any version that matches group:org.test, module:projectA, version:latest.release.'
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '1.0').withStatus('release').publish()
-        ivyRepo.module('org.test', 'projectA', '1.1').withStatus('release').publish()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.1.jar')
-    }
-
-    @Ignore
-    @Issue("GRADLE-2502")
-    public void "version selector ending in + selects highest matching version"() {
-        given:
-        buildFile << """
-  repositories {
-      ivy {
-          url "${ivyRepo.uri}"
-      }
-  }
-  configurations { compile }
-  dependencies {
-      compile 'org.test:projectA:1.2+'
-  }
-  task retrieve(type: Sync) {
-      from configurations.compile
-      into 'libs'
-  }
-  """
-        and:
-        ivyRepo.module('org.test', 'projectA', '1.1.2').publish()
-        ivyRepo.module('org.test', 'projectA', '2.0').publish()
-
-        when:
-        runAndFail 'retrieve'
-
-        then:
-        failureHasCause 'Could not find any version that matches group:org.test, module:projectA, version:1.2+'
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '1.2').withNoMetaData().publish()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '1.2.1').publish()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.1.jar')
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '1.2.9').publish()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.9.jar')
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '1.2.12').withNoMetaData().publish()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.12.jar')
-    }
-
-    @Ignore
-    @Issue("GRADLE-2502")
-    public void "version range selects highest matching version"() {
-        given:
-        buildFile << """
-  repositories {
-      ivy {
-          url "${ivyRepo.uri}"
-      }
-  }
-  configurations { compile }
-  dependencies {
-      compile 'org.test:projectA:[1.2,2.0]'
-  }
-  task retrieve(type: Sync) {
-      from configurations.compile
-      into 'libs'
-  }
-  """
-        and:
-        ivyRepo.module('org.test', 'projectA', '1.1.2').publish()
-        ivyRepo.module('org.test', 'projectA', '2.0').publish()
-
-        when:
-        runAndFail 'retrieve'
-
-        then:
-        failureHasCause 'Could not find any version that matches group:org.test, module:projectA, version:[1.2,2.0]'
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '1.2').withNoMetaData().publish()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '1.2.1').publish()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.1.jar')
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '1.3').publish()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.3.jar')
-
-        when:
-        ivyRepo.module('org.test', 'projectA', '1.3.12').withNoMetaData().publish()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.3.12.jar')
-    }
-
-    @Issue("GRADLE-2502")
-    public void "can resolve dynamic version from different repositories"() {
-        given:
-        def repo1 = ivyRepo("ivyRepo1")
-        def repo2 = ivyRepo("ivyRepo2")
-        repo1.module('org.test', 'projectA', '1.1').withStatus("milestone").publish()
-        repo2.module('org.test', 'projectA', '1.2').withStatus("integration").publish()
-
-        and:
-        buildFile << """
-  repositories {
-      ivy {
-          url "${repo1.uri}"
-      }
-      ivy {
-          url "${repo2.uri}"
-      }
-
-  }
-  configurations { compile }
-  dependencies {
-      compile 'org.test:projectA:latest.milestone'
-  }
-  task retrieve(type: Sync) {
-      from configurations.compile
-      into 'libs'
-  }
-  """
-
-        when:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.1.jar')
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyFileRepoResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyFileRepoResolveIntegrationTest.groovy
deleted file mode 100644
index 13983c9..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyFileRepoResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.ivy
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class IvyFileRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
-    public void "does not cache local artifacts or metadata"() {
-        given:
-        def repo = ivyRepo()
-        def moduleA = repo.module('group', 'projectA', '1.2')
-        moduleA.publish()
-        def moduleB = repo.module('group', 'projectB', '9-beta')
-        moduleB.publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy {
-        artifactPattern "${repo.uri}/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.2'
-}
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        when:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-        file('libs/projectA-1.2.jar').assertIsCopyOf(moduleA.jarFile)
-
-        when:
-        moduleA.dependsOn('group', 'projectB', '9-beta')
-        moduleA.publishWithChangedContent()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-9-beta.jar')
-        file('libs/projectA-1.2.jar').assertIsCopyOf(moduleA.jarFile)
-        file('libs/projectB-9-beta.jar').assertIsCopyOf(moduleB.jarFile)
-    }
-
-    public void "does not cache resolution of dynamic versions or changing modules"() {
-        def repo = ivyRepo()
-
-        given:
-        buildFile << """
-repositories {
-    ivy {
-        artifactPattern "${repo.uri}/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
-    }
-}
-
-configurations {
-    compile
-}
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.+"
-    compile group: "group", name: "projectB", version: "latest.integration"
-    compile group: "group", name: "projectC", version: "1.0", changing: true
-}
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        when:
-        def projectA1 = repo.module("group", "projectA", "1.1")
-        projectA1.publish()
-        def projectB1 = repo.module("group", "projectB", "1.0")
-        projectB1.publish()
-        def projectC1 = repo.module("group", "projectC", "1.0")
-        projectC1.publish()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.1.jar', 'projectB-1.0.jar', 'projectC-1.0.jar')
-        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
-        file('libs/projectB-1.0.jar').assertIsCopyOf(projectB1.jarFile)
-        def jarC = file('libs/projectC-1.0.jar')
-        jarC.assertIsCopyOf(projectC1.jarFile)
-        def jarCsnapshot = jarC.snapshot()
-
-        when:
-        def projectA2 = repo.module("group", "projectA", "1.2")
-        projectA2.publish()
-        def projectB2 = repo.module("group", "projectB", "2.0")
-        projectB2.publish()
-        projectC1.publishWithChangedContent()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-2.0.jar', 'projectC-1.0.jar')
-        file('libs/projectA-1.2.jar').assertIsCopyOf(projectA2.jarFile)
-        file('libs/projectB-2.0.jar').assertIsCopyOf(projectB2.jarFile)
-
-        def jarC1 = file('libs/projectC-1.0.jar')
-        jarC1.assertIsCopyOf(projectC1.jarFile)
-        jarC1.assertHasChangedSince(jarCsnapshot)
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy
deleted file mode 100644
index 0c1475a..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.ivy
-
-import org.gradle.integtests.fixtures.ProgressLoggingFixture
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import org.junit.Rule
-
-class IvyHttpRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
-
-    @Rule ProgressLoggingFixture progressLogger
-
-    public void "can resolve and cache dependencies from an HTTP Ivy repository"() {
-        server.start()
-        given:
-        def module = ivyRepo().module('group', 'projectA', '1.2').publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.2' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-        when:
-        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
-        then:
-        succeeds 'listJars'
-        progressLogger.downloadProgressLogged("http://localhost:${server.port}/repo/group/projectA/1.2/ivy-1.2.xml")
-        progressLogger.downloadProgressLogged("http://localhost:${server.port}/repo/group/projectA/1.2/projectA-1.2.jar")
-        when:
-        server.resetExpectations()
-        then:
-        succeeds 'listJars'
-    }
-
-    public void "can resolve and cache artifact-only dependencies from an HTTP Ivy repository"() {
-        server.start()
-        given:
-        def module = ivyRepo().module('group', 'projectA', '1.2').publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.2 at jar' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-
-
-        when:
-        // TODO: Should meta-data be fetched for an artifact-only dependency?
-        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
-
-        then:
-        executer.withArgument("-i")
-        succeeds('listJars')
-
-        when:
-        server.resetExpectations()
-        // No extra calls for cached dependencies
-
-        then:
-        executer.withArgument("-i")
-        succeeds('listJars')
-    }
-
-    public void "can resolve and cache dependencies from multiple HTTP Ivy repositories"() {
-        server.start()
-        given:
-        def repo = ivyRepo()
-        def moduleA = repo.module('group', 'projectA').publish()
-        def moduleB = repo.module('group', 'projectB').publish()
-        def moduleC = repo.module('group', 'projectC').publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo1" }
-    ivy { url "http://localhost:${server.port}/repo2" }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.0', 'group:projectB:1.0', 'group:projectC:1.0'
-}
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar', 'projectB-1.0.jar', 'projectC-1.0.jar']
-}
-"""
-
-        when:
-        server.expectGet('/repo1/group/projectA/1.0/ivy-1.0.xml', moduleA.ivyFile)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', moduleA.jarFile)
-
-        // Handles missing in repo1
-        server.expectGetMissing('/repo1/group/projectB/1.0/ivy-1.0.xml')
-        server.expectHeadMissing('/repo1/group/projectB/1.0/projectB-1.0.jar')
-
-        server.expectGet('/repo2/group/projectB/1.0/ivy-1.0.xml', moduleB.ivyFile)
-        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.jar', moduleB.jarFile)
-
-        // Handles from broken url in repo1 (but does not cache)
-        server.addBroken('/repo1/group/projectC')
-        server.expectGet('/repo2/group/projectC/1.0/ivy-1.0.xml', moduleC.ivyFile)
-        server.expectGet('/repo2/group/projectC/1.0/projectC-1.0.jar', moduleC.jarFile)
-        then:
-        succeeds('listJars')
-        when:
-        server.resetExpectations()
-        server.addBroken('/repo1/group/projectC') // Will always re-attempt a broken repository
-        // No extra calls for cached dependencies
-
-        then:
-        succeeds('listJars')
-    }
-
-    public void "uses all configured patterns to resolve artifacts and caches result"() {
-        server.start()
-
-        given:
-        def module = ivyRepo().module('group', 'projectA', '1.2').publish()
-
-        buildFile << """
-repositories {
-    ivy {
-        url "http://localhost:${server.port}/first"
-        artifactPattern "http://localhost:${server.port}/second/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"
-        artifactPattern "http://localhost:${server.port}/third/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"
-        ivyPattern "http://localhost:${server.port}/second/[module]/[revision]/ivy.xml"
-        ivyPattern "http://localhost:${server.port}/third/[module]/[revision]/ivy.xml"
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.2'
-}
-task show << { println configurations.compile.files }
-"""
-
-        when:
-        server.expectGetMissing('/first/group/projectA/1.2/ivy-1.2.xml')
-        server.expectGetMissing('/first/group/projectA/1.2/projectA-1.2.jar')
-        server.expectGetMissing('/second/projectA/1.2/ivy.xml')
-        server.expectGetMissing('/second/projectA/1.2/projectA-1.2.jar')
-        server.expectGet('/third/projectA/1.2/ivy.xml', module.ivyFile)
-        server.expectGet('/third/projectA/1.2/projectA-1.2.jar', module.jarFile)
-
-        then:
-        succeeds('show')
-
-        when:
-        server.resetExpectations()
-
-        then:
-        succeeds('show')
-    }
-
-    public void "replaces org.name with org/name when using maven layout"() {
-        server.start()
-
-        given:
-        def module = ivyRepo().module('org.name.here', 'projectA', '1.2').publish()
-
-        buildFile << """
-repositories {
-    ivy {
-        url "http://localhost:${server.port}"
-        layout "maven"
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'org.name.here:projectA:1.2'
-}
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        when:
-        server.expectGet('/org/name/here/projectA/1.2/ivy-1.2.xml', module.ivyFile)
-        server.expectGet('/org/name/here/projectA/1.2/projectA-1.2.jar', module.jarFile)
-
-        and:
-        succeeds('retrieve')
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyResolveIntegrationTest.groovy
deleted file mode 100644
index 5c60cce..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.ivy
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class IvyResolveIntegrationTest extends AbstractDependencyResolutionTest {
-
-    def "dependency includes all artifacts and transitive dependencies of referenced configuration"() {
-        given:
-        def repo = ivyRepo()
-        def module = repo.module("org.gradle", "test", "1.45")
-        module.dependsOn("org.gradle", "other", "preview-1")
-        module.artifact(classifier: "classifier")
-        module.artifact(name: "test-extra")
-        module.publish()
-        repo.module("org.gradle", "other", "preview-1").publish()
-
-        and:
-        buildFile << """
-repositories { ivy { url "${repo.uri}" } }
-configurations { compile }
-dependencies {
-    compile "org.gradle:test:1.45"
-}
-
-task check << {
-    assert configurations.compile.collect { it.name } == ['test-1.45.jar', 'test-1.45-classifier.jar', 'test-extra-1.45.jar', 'other-preview-1.jar']
-}
-"""
-
-        expect:
-        succeeds "check"
-    }
-
-    def "dependency that references a classifier includes the matching artifact only plus the transitive dependencies of referenced configuration"() {
-        given:
-        def repo = ivyRepo()
-        repo.module("org.gradle", "test", "1.45")
-                .dependsOn("org.gradle", "other", "preview-1")
-                .artifact(classifier: "classifier")
-                .artifact(name: "test-extra")
-                .publish()
-        repo.module("org.gradle", "other", "preview-1").publish()
-
-        and:
-        buildFile << """
-repositories { ivy { url "${repo.uri}" } }
-configurations { compile }
-dependencies {
-    compile "org.gradle:test:1.45:classifier"
-}
-
-task check << {
-    assert configurations.compile.collect { it.name } == ['test-1.45-classifier.jar', 'other-preview-1.jar']
-}
-"""
-
-        expect:
-        succeeds "check"
-    }
-
-    def "dependency that references an artifact includes the matching artifact only plus the transitive dependencies of referenced configuration"() {
-        given:
-        def repo = ivyRepo()
-        def module = repo.module("org.gradle", "test", "1.45")
-        module.dependsOn("org.gradle", "other", "preview-1")
-        module.artifact(classifier: "classifier")
-        module.artifact(name: "test-extra")
-        module.publish()
-        repo.module("org.gradle", "other", "preview-1").publish()
-
-        and:
-        buildFile << """
-repositories { ivy { url "${repo.uri}" } }
-configurations { compile }
-dependencies {
-    compile ("org.gradle:test:1.45") {
-        artifact {
-            name = 'test-extra'
-            type = 'jar'
-        }
-    }
-}
-
-task check << {
-    assert configurations.compile.collect { it.name } == ['test-extra-1.45.jar', 'other-preview-1.jar']
-}
-"""
-
-        expect:
-        succeeds "check"
-    }
-
-    def "transitive flag of referenced configuration affects its transitive dependencies only"() {
-        given:
-        def repo = ivyRepo()
-        def module = repo.module("org.gradle", "test", "1.45")
-        module.dependsOn("org.gradle", "other", "preview-1")
-        module.nonTransitive('default')
-        module.publish()
-        repo.module("org.gradle", "other", "preview-1").dependsOn("org.gradle", "other2", "7").publish()
-        repo.module("org.gradle", "other2", "7").publish()
-
-        and:
-        buildFile << """
-repositories { ivy { url "${repo.uri}" } }
-configurations {
-    compile
-    runtime.extendsFrom compile
-}
-dependencies {
-    compile "org.gradle:test:1.45"
-    runtime "org.gradle:other:preview-1"
-}
-
-task check << {
-    def spec = { it.name == 'test' } as Spec
-
-    assert configurations.compile.collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
-    assert configurations.compile.resolvedConfiguration.getFiles(spec).collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
-
-    assert configurations.runtime.collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar', 'other2-7.jar']
-    assert configurations.compile.resolvedConfiguration.getFiles(spec).collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
-}
-"""
-
-        expect:
-        succeeds "check"
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileResolveIntegrationTest.groovy
deleted file mode 100644
index a94a992..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.maven
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import spock.lang.Issue
-
-class BadPomFileResolveIntegrationTest extends AbstractDependencyResolutionTest {
-
-    @Issue("http://issues.gradle.org/browse/GRADLE-1005")
-    def "can handle self referencing dependency"() {
-        given:
-        file("settings.gradle") << "include 'client'"
-
-        and:
-        mavenRepo().module('group', 'artifact', '1.0').dependsOn('group', 'artifact', '1.0').publish()
-
-        and:
-        buildFile << """
-            repositories {
-                maven { url "${mavenRepo().uri}" }
-            }
-            configurations { compile }
-            dependencies {
-                compile "group:artifact:1.0"
-            }
-            task libs << { assert configurations.compile.files.collect {it.name} == ['artifact-1.0.jar'] }
-        """
-
-        expect:
-        succeeds ":libs"
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDependencyResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDependencyResolveIntegrationTest.groovy
deleted file mode 100644
index 6d83cdd..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDependencyResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.maven
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class MavenDependencyResolveIntegrationTest extends AbstractDependencyResolutionTest {
-    def "dependency includes main artifact and runtime dependencies of referenced module"() {
-        given:
-        def module = mavenRepo().module("org.gradle", "test", "1.45")
-        module.dependsOn("org.gradle", "other", "preview-1")
-        module.artifact(classifier: 'classifier')
-        module.publish()
-        mavenRepo().module("org.gradle", "other", "preview-1").publish()
-
-        and:
-        buildFile << """
-repositories { maven { url "${mavenRepo().uri}" } }
-configurations { compile }
-dependencies {
-    compile "org.gradle:test:1.45"
-}
-
-task check << {
-    assert configurations.compile.collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
-}
-"""
-
-        expect:
-        succeeds "check"
-    }
-
-    def "dependency that references a classifier includes the matching artifact only plus the runtime dependencies of referenced module"() {
-        given:
-        def module = mavenRepo().module("org.gradle", "test", "1.45")
-        module.dependsOn("org.gradle", "other", "preview-1")
-        module.artifact(classifier: 'classifier')
-        module.artifact(classifier: 'some-other')
-        module.publish()
-        mavenRepo().module("org.gradle", "other", "preview-1").publish()
-
-        and:
-        buildFile << """
-repositories { maven { url "${mavenRepo().uri}" } }
-configurations { compile }
-dependencies {
-    compile "org.gradle:test:1.45:classifier"
-}
-
-task check << {
-    assert configurations.compile.collect { it.name } == ['test-1.45-classifier.jar', 'other-preview-1.jar']
-}
-"""
-
-        expect:
-        succeeds "check"
-    }
-
-    def "dependency that references an artifact includes the matching artifact only plus the runtime dependencies of referenced module"() {
-        given:
-        def module = mavenRepo().module("org.gradle", "test", "1.45")
-        module.dependsOn("org.gradle", "other", "preview-1")
-        module.artifact(classifier: 'classifier')
-        module.publish()
-        mavenRepo().module("org.gradle", "other", "preview-1").publish()
-
-        and:
-        buildFile << """
-repositories { maven { url "${mavenRepo().uri}" } }
-configurations { compile }
-dependencies {
-    compile ("org.gradle:test:1.45") {
-        artifact {
-            name = 'test'
-            type = 'jar'
-            classifier = 'classifier'
-        }
-    }
-}
-
-task check << {
-    assert configurations.compile.collect { it.name } == ['test-1.45-classifier.jar', 'other-preview-1.jar']
-}
-"""
-
-        expect:
-        succeeds "check"
-    }
-
-    def "resolves dependencies on real projects"() {
-        // Hibernate core brings in conflicts, exclusions and parent poms
-        // Add a direct dependency on an earlier version of commons-collection than required by hibernate core
-        // Logback classic depends on a later version of slf4j-api than required by hibernate core
-
-        given:
-        buildFile << """
-repositories {
-    mavenCentral()
-}
-
-configurations {
-    compile
-}
-
-dependencies {
-    compile "commons-collections:commons-collections:3.0"
-    compile "ch.qos.logback:logback-classic:0.9.30"
-    compile "org.hibernate:hibernate-core:3.6.7.Final"
-}
-
-task check << {
-    def compile = configurations.compile
-    assert compile.resolvedConfiguration.firstLevelModuleDependencies.collect { it.name } == [
-        'ch.qos.logback:logback-classic:0.9.30',
-        'org.hibernate:hibernate-core:3.6.7.Final',
-        'commons-collections:commons-collections:3.1'
-    ]
-
-    def filteredDependencies = compile.resolvedConfiguration.getFirstLevelModuleDependencies({ it.name == 'logback-classic' } as Spec)
-    assert filteredDependencies.collect { it.name } == [
-        'ch.qos.logback:logback-classic:0.9.30'
-    ]
-
-    def filteredFiles = compile.resolvedConfiguration.getFiles({ it.name == 'logback-classic' } as Spec)
-    assert filteredFiles.collect { it.name } == [
-        'logback-classic-0.9.30.jar',
-         'logback-core-0.9.30.jar',
-          'slf4j-api-1.6.2.jar'
-    ]
-
-    assert compile.collect { it.name } == [
-        'logback-classic-0.9.30.jar',
-        'hibernate-core-3.6.7.Final.jar',
-        'commons-collections-3.1.jar',
-        'logback-core-0.9.30.jar',
-        'slf4j-api-1.6.2.jar',
-        'antlr-2.7.6.jar',
-        'dom4j-1.6.1.jar',
-        'hibernate-commons-annotations-3.2.0.Final.jar',
-        'hibernate-jpa-2.0-api-1.0.1.Final.jar',
-        'jta-1.1.jar'
-    ]
-
-    assert compile.resolvedConfiguration.resolvedArtifacts.collect { it.file.name } == [
-        'logback-classic-0.9.30.jar',
-        'hibernate-core-3.6.7.Final.jar',
-        'logback-core-0.9.30.jar',
-        'slf4j-api-1.6.2.jar',
-        'antlr-2.7.6.jar',
-        'dom4j-1.6.1.jar',
-        'hibernate-commons-annotations-3.2.0.Final.jar',
-        'hibernate-jpa-2.0-api-1.0.1.Final.jar',
-        'jta-1.1.jar',
-        'commons-collections-3.1.jar'
-    ]
-}
-"""
-
-        expect:
-        succeeds "check"
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDynamicResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDynamicResolveIntegrationTest.groovy
deleted file mode 100644
index ea90001..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDynamicResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.resolve.maven
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class MavenDynamicResolveIntegrationTest extends AbstractDependencyResolutionTest {
-
-    def "can resolve dynamic version declared in pom as transitive dependency from HTTP Maven repository"() {
-        given:
-        server.start()
-
-        mavenHttpRepo.module('org.test', 'projectC', '1.1').publish()
-        def projectC = mavenHttpRepo.module('org.test', 'projectC', '1.5').publish()
-        mavenHttpRepo.module('org.test', 'projectC', '2.0').publish()
-        def projectB = mavenHttpRepo.module('org.test', 'projectB', '1.0').dependsOn("org.test", 'projectC', '[1.0, 2.0)').publish()
-        def projectA = mavenHttpRepo.module('org.test', 'projectA', '1.0').dependsOn('org.test', 'projectB', '1.0').publish()
-
-        buildFile << """
-    repositories {
-        maven { url '${mavenHttpRepo.uri}' }
-    }
-    configurations { compile }
-    dependencies {
-        compile 'org.test:projectA:1.0'
-    }
-
-    task retrieve(type: Sync) {
-        into 'libs'
-        from configurations.compile
-    }
-    """
-
-        when:
-        projectA.expectPomGet()
-        projectA.expectArtifactGet()
-        projectB.expectPomGet()
-        projectB.expectArtifactGet()
-        mavenHttpRepo.expectMetaDataGet("org.test", "projectC")
-        projectC.expectPomGet()
-        projectC.expectArtifactGet()
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.0.jar', 'projectB-1.0.jar', 'projectC-1.5.jar')
-        def snapshot = file('libs/projectA-1.0.jar').snapshot()
-
-        when:
-        server.resetExpectations()
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
-    }
-
-    def "falls back to directory listing when maven-metadata.xml is missing"() {
-        given:
-        server.start()
-        mavenHttpRepo.module('org.test', 'projectA', '1.0').publish()
-        def projectA = mavenHttpRepo.module('org.test', 'projectA', '1.5').publish()
-
-        buildFile << """
-    repositories {
-        maven { url '${mavenHttpRepo.uri}' }
-    }
-    configurations { compile }
-    dependencies {
-        compile 'org.test:projectA:1.+'
-    }
-
-    task retrieve(type: Sync) {
-        into 'libs'
-        from configurations.compile
-    }
-    """
-
-        when:
-        mavenHttpRepo.expectMetaDataGetMissing("org.test", "projectA")
-        mavenHttpRepo.expectDirectoryListGet("org.test", "projectA")
-        projectA.expectPomGet()
-        projectA.expectArtifactGet()
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.5.jar')
-        def snapshot = file('libs/projectA-1.5.jar').snapshot()
-
-        when:
-        server.resetExpectations()
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs/projectA-1.5.jar').assertHasNotChangedSince(snapshot)
-    }
-
-    def "does not cache broken module information"() {
-        given:
-        server.start()
-        def repo1 = mavenHttpRepo("repo1")
-        def repo2 = mavenHttpRepo("repo2")
-        def projectA1 = repo1.module('group', 'projectA', '1.1').publish()
-        def projectA2 = repo2.module('group', 'projectA', '1.5').publish()
-
-        buildFile << """
-        repositories {
-            maven { url '${repo1.uri}' }
-            maven { url '${repo2.uri}' }
-        }
-        configurations { compile }
-        dependencies {
-            compile 'group:projectA:1.+'
-        }
-
-        task retrieve(type: Sync) {
-            into 'libs'
-            from configurations.compile
-        }
-        """
-
-        when:
-        repo1.expectMetaDataGet("group", "projectA")
-        projectA1.expectPomGet()
-        projectA1.expectArtifactGet()
-
-        repo2.expectMetaDataGet("group", "projectA")
-        server.addBroken('/repo2/group/projectA/1.5/projectA-1.5.pom')
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.1.jar')
-
-        when:
-        server.resetExpectations()
-        repo2.expectMetaDataGet("group", "projectA")
-        projectA2.expectPomGet()
-        projectA2.expectArtifactGet()
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.5.jar')
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenFileRepoResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenFileRepoResolveIntegrationTest.groovy
deleted file mode 100644
index 8ccd851..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenFileRepoResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.maven
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class MavenFileRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
-    public void "can resolve snapshots uncached from local Maven repository"() {
-        given:
-        def moduleA = mavenRepo().module('group', 'projectA', '1.2-SNAPSHOT')
-        def moduleB = mavenRepo().module('group', 'projectB', '9.1')
-        moduleA.publish()
-        moduleB.publish()
-
-        and:
-        buildFile << """
-configurations { compile }
-repositories { maven { url "${mavenRepo().uri}" } }
-dependencies { compile 'group:projectA:1.2-SNAPSHOT' }
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'build'
-}
-"""
-
-        when:
-        run 'retrieve'
-
-        then:
-        def buildDir = file('build')
-        buildDir.assertHasDescendants(moduleA.artifactFile.name)
-        buildDir.file(moduleA.artifactFile.name).assertIsCopyOf(moduleA.artifactFile)
-
-        when:
-        moduleA.dependsOn('group', 'projectB', '9.1')
-        moduleA.publishWithChangedContent()
-        run 'retrieve'
-
-        then:
-        buildDir.assertHasDescendants(moduleA.artifactFile.name, 'projectB-9.1.jar')
-        buildDir.file(moduleA.artifactFile.name).assertIsCopyOf(moduleA.artifactFile)
-        buildDir.file('projectB-9.1.jar').assertIsCopyOf(moduleB.artifactFile)
-    }
-
-    public void "does not cache artifacts and metadata from local Maven repository"() {
-        given:
-        def moduleA = mavenRepo().module('group', 'projectA', '1.2')
-        def moduleB = mavenRepo().module('group', 'projectB', '9.1')
-        moduleA.publish()
-        moduleB.publish()
-
-        and:
-        buildFile << """
-configurations { compile }
-repositories { maven { url "${mavenRepo().uri}" } }
-dependencies { compile 'group:projectA:1.2' }
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'build'
-}
-"""
-
-        when:
-        run 'retrieve'
-
-        then:
-        def buildDir = file('build')
-        buildDir.assertHasDescendants('projectA-1.2.jar')
-        buildDir.file('projectA-1.2.jar').assertIsCopyOf(moduleA.artifactFile)
-
-        when:
-        moduleA.dependsOn('group', 'projectB', '9.1')
-        moduleA.publishWithChangedContent()
-        run 'retrieve'
-
-        then:
-        buildDir.assertHasDescendants('projectA-1.2.jar', 'projectB-9.1.jar')
-        buildDir.file('projectA-1.2.jar').assertIsCopyOf(moduleA.artifactFile)
-        buildDir.file('projectB-9.1.jar').assertIsCopyOf(moduleB.artifactFile)
-    }
-
-    public void "uses artifactUrls to resolve artifacts"() {
-        given:
-        def moduleA = mavenRepo().module('group', 'projectA', '1.2')
-        def moduleB = mavenRepo().module('group', 'projectB', '9.1')
-        moduleA.publish()
-        moduleB.publish()
-
-        def artifactsRepo = mavenRepo('artifactsRepo')
-        // Create a module to get the correct module directory, but do not publish the module
-        def artifactsModuleA = artifactsRepo.module('group', 'projectA', '1.2')
-        moduleA.artifactFile.moveToDirectory(artifactsModuleA.moduleDir)
-
-        and:
-        buildFile << """
-repositories {
-    maven {
-        url "${mavenRepo().uri}"
-        artifactUrls "${artifactsRepo.uri}"
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.2'
-    compile 'group:projectB:9.1'
-}
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'build'
-}
-"""
-
-        when:
-        run 'retrieve'
-
-        then:
-        def buildDir = file('build')
-        buildDir.assertHasDescendants('projectA-1.2.jar', 'projectB-9.1.jar')
-        buildDir.file('projectA-1.2.jar').assertIsCopyOf(artifactsModuleA.artifactFile)
-        buildDir.file('projectB-9.1.jar').assertIsCopyOf(moduleB.artifactFile)
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy
deleted file mode 100644
index 2eb0590..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.maven
-
-import org.gradle.integtests.fixtures.ProgressLoggingFixture
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import org.junit.Rule
-
-class MavenHttpRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
-
-    @Rule ProgressLoggingFixture progressLogging
-
-    def "can resolve and cache dependencies from HTTP Maven repository"() {
-        given:
-        server.start()
-
-        def projectB = mavenRepo().module('group', 'projectB').publish()
-        def projectA = mavenRepo().module('group', 'projectA').dependsOn('projectB').publish()
-
-        buildFile << """
-repositories {
-    maven { url 'http://localhost:${server.port}/repo1' }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.0'
-}
-
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-
-        when:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
-        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.0.jar', 'projectB-1.0.jar')
-        def snapshot = file('libs/projectA-1.0.jar').snapshot()
-
-        and:
-        progressLogging.downloadProgressLogged("http://localhost:${server.port}/repo1/group/projectA/1.0/projectA-1.0.pom")
-        progressLogging.downloadProgressLogged("http://localhost:${server.port}/repo1/group/projectA/1.0/projectA-1.0.jar")
-        progressLogging.downloadProgressLogged("http://localhost:${server.port}/repo1/group/projectB/1.0/projectB-1.0.pom")
-        progressLogging.downloadProgressLogged("http://localhost:${server.port}/repo1/group/projectB/1.0/projectB-1.0.jar")
-
-        when:
-        server.resetExpectations()
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
-    }
-
-    def "can resolve and cache artifact-only dependencies from an HTTP Maven repository"() {
-        server.start()
-        given:
-        def module = mavenRepo().module('group', 'projectA', '1.2')
-        module.publish()
-
-        and:
-        buildFile << """
-repositories {
-    maven { url "http://localhost:${server.port}/repo1" }
-    maven { url "http://localhost:${server.port}/repo2" }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.2 at jar' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-
-        when:
-        // TODO: Should meta-data be fetched for an artifact-only dependency?
-        server.expectGetMissing('/repo1/group/projectA/1.2/projectA-1.2.pom')
-        server.expectHeadMissing('/repo1/group/projectA/1.2/projectA-1.2.jar')
-
-        server.expectGet('/repo2/group/projectA/1.2/projectA-1.2.pom', module.pomFile)
-        server.expectGet('/repo2/group/projectA/1.2/projectA-1.2.jar', module.artifactFile)
-
-        then:
-        succeeds('listJars')
-
-        when:
-        server.resetExpectations()
-        // No extra calls for cached dependencies
-
-        then:
-        succeeds('listJars')
-    }
-
-    def "does not download source and javadoc artifacts from HTTP Maven repository until required"() {
-        given:
-        server.start()
-
-        def projectA = mavenRepo().module('group', 'projectA', '1.0')
-        projectA.artifact(classifier: 'sources')
-        projectA.artifact(classifier: 'javadoc')
-        projectA.publish()
-        def sourceJar = projectA.artifactFile(classifier: 'sources')
-        def javadocJar = projectA.artifactFile(classifier: 'javadoc')
-
-        buildFile << """
-apply plugin: 'java'
-apply plugin: 'eclipse'
-repositories {
-    maven { url 'http://localhost:${server.port}/repo1' }
-}
-dependencies {
-    compile 'group:projectA:1.0'
-}
-eclipse { classpath { downloadJavadoc = true } }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
-}
-"""
-
-        when:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-
-        then:
-        succeeds 'listJars'
-
-        when:
-        server.resetExpectations()
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0-sources.jar', sourceJar)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0-javadoc.jar', javadocJar)
-
-        then:
-        succeeds 'eclipseClasspath'
-    }
-
-    def "only attempts to download missing artifacts from HTTP Maven repository once"() {
-        given:
-        server.start()
-
-        def projectA = mavenRepo().module('group', 'projectA', '1.0')
-        projectA.publish()
-
-        buildFile << """
-apply plugin: 'java'
-apply plugin: 'eclipse'
-repositories {
-    maven { url 'http://localhost:${server.port}/repo1' }
-}
-dependencies {
-    compile 'group:projectA:1.0'
-}
-eclipse { classpath { downloadJavadoc = true } }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
-}
-"""
-
-        when:
-        server.resetExpectations()
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0-sources.jar')
-        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0-javadoc.jar')
-
-        then:
-        succeeds 'eclipseClasspath'
-
-        when:
-        server.resetExpectations()
-
-        then:
-        succeeds 'eclipseClasspath'
-    }
-
-    def "can resolve and cache dependencies from multiple HTTP Maven repositories"() {
-        given:
-        server.start()
-
-        buildFile << """
-repositories {
-    maven { url 'http://localhost:${server.port}/repo1' }
-    maven { url 'http://localhost:${server.port}/repo2' }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.0', 'group:projectB:1.0'
-}
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar', 'projectB-1.0.jar']
-}
-"""
-
-        def projectA = mavenRepo().module('group', 'projectA').publish()
-        def projectB = mavenRepo().module('group', 'projectB').publish()
-
-        when:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-
-        // Looks for POM and JAR in repo1 before looking in repo2 (jar is an attempt to handle publication without module descriptor)
-        server.expectGetMissing('/repo1/group/projectB/1.0/projectB-1.0.pom')
-        server.expectHeadMissing('/repo1/group/projectB/1.0/projectB-1.0.jar')
-        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
-
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
-
-        then:
-        succeeds 'listJars'
-
-        when:
-        server.resetExpectations()
-        // No server requests when all jars cached
-
-        then:
-        succeeds 'listJars'
-    }
-
-    def "uses artifactsUrl to resolve artifacts"() {
-        given:
-        server.start()
-
-        buildFile << """
-repositories {
-    maven {
-        url 'http://localhost:${server.port}/repo1'
-        artifactUrls 'http://localhost:${server.port}/repo2'
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.0', 'group:projectB:1.0'
-}
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar', 'projectB-1.0.jar']
-}
-"""
-
-        def projectA = mavenRepo().module('group', 'projectA')
-        def projectB = mavenRepo().module('group', 'projectB')
-        projectA.publish()
-        projectB.publish()
-
-        when:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
-
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-        server.expectGetMissing('/repo1/group/projectB/1.0/projectB-1.0.jar')
-        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
-
-        then:
-        succeeds 'listJars'
-    }
-
-    def "can resolve and cache dependencies from HTTP Maven repository with invalid settings.xml"() {
-        given:
-        server.start()
-
-        def projectB = mavenRepo().module('group', 'projectB').publish()
-        def projectA = mavenRepo().module('group', 'projectA').dependsOn('projectB').publish()
-
-        buildFile << """
-    repositories {
-        maven { url 'http://localhost:${server.port}/repo1' }
-    }
-    configurations { compile }
-    dependencies {
-        compile 'group:projectA:1.0'
-    }
-
-    task retrieve(type: Sync) {
-        into 'libs'
-        from configurations.compile
-    }
-    """
-
-        def m2Home = file("M2_REPO")
-        def settingsFile = m2Home.file("conf/settings.xml")
-        settingsFile << "invalid content... blabla"
-
-        when:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
-        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
-
-        and:
-
-        executer.withEnvironmentVars(M2_HOME: m2Home.absolutePath)
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.0.jar', 'projectB-1.0.jar')
-        def snapshot = file('libs/projectA-1.0.jar').snapshot()
-
-        when:
-        server.resetExpectations()
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy
deleted file mode 100644
index e59b1a7..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.maven
-
-import org.gradle.integtests.fixture.M2Installation
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.MavenModule
-import org.gradle.util.SetSystemProperties
-import org.junit.Rule
-
-import static org.hamcrest.Matchers.containsString
-
-class MavenLocalRepoResolveIntegrationTest extends AbstractIntegrationSpec {
-
-    @Rule SetSystemProperties sysProp = new SetSystemProperties()
-
-    public void setup() {
-        requireOwnUserHomeDir()
-        buildFile << """
-                repositories {
-                    mavenLocal()
-                }
-                configurations { compile }
-                dependencies {
-                    compile 'group:projectA:1.2'
-                }
-
-                task retrieve(type: Sync) {
-                    from configurations.compile
-                    into 'build'
-                }"""
-    }
-
-    public void "can resolve artifacts from local m2 when user settings.xml does not exist"() {
-        given:
-        def m2 = localM2()
-        def moduleA = m2.mavenRepo().module('group', 'projectA', '1.2').publish()
-
-        and:
-        withM2(m2)
-
-        when:
-        run 'retrieve'
-
-        then:
-        hasArtifact(moduleA)
-
-    }
-
-    public void "can resolve artifacts from local m2 with custom local repository defined in user settings.xml"() {
-        given:
-        def artifactRepo = maven("artifactrepo")
-        def m2 = localM2().generateUserSettingsFile(artifactRepo)
-        def moduleA = artifactRepo.module('group', 'projectA', '1.2').publish()
-
-        and:
-        withM2(m2)
-
-        when:
-        run 'retrieve'
-
-        then:
-        hasArtifact(moduleA)
-    }
-
-    public void "can resolve artifacts from local m2 with custom local repository defined in global settings.xml"() {
-        given:
-        def artifactRepo = maven("artifactrepo")
-        def m2 = localM2().generateGlobalSettingsFile(artifactRepo)
-        def moduleA = artifactRepo.module('group', 'projectA', '1.2').publish()
-
-        and:
-        withM2(m2)
-
-        when:
-        run 'retrieve'
-
-        then:
-        hasArtifact(moduleA)
-    }
-
-    public void "local repository in user settings take precedence over the local repository global settings"() {
-        given:
-        def globalRepo = maven("globalArtifactRepo")
-        def userRepo = maven("userArtifactRepo")
-        def m2 = localM2().generateGlobalSettingsFile(globalRepo).generateUserSettingsFile(userRepo)
-        def moduleA = userRepo.module('group', 'projectA', '1.2').publish()
-        globalRepo.module('group', 'projectA', '1.2').publishWithChangedContent()
-
-        and:
-        withM2(m2)
-
-        when:
-        run 'retrieve'
-
-        then:
-        hasArtifact(moduleA)
-    }
-
-    public void "fail with meaningful error message if settings.xml is invalid"() {
-        given:
-        def m2 = localM2()
-        m2.userSettingsFile << "invalid content"
-
-        and:
-        withM2(m2)
-
-        when:
-        def failure = runAndFail('retrieve')
-
-        then:
-        failure.assertThatCause(containsString(String.format("Non-parseable settings %s:", m2.userSettingsFile.absolutePath)));
-    }
-
-    public void "mavenLocal is ignored if no local maven repository exists"() {
-        given:
-        def m2 = localM2()
-        def anotherRepo = maven("another-local-repo")
-        def moduleA = anotherRepo.module('group', 'projectA', '1.2').publishWithChangedContent()
-
-        and:
-        buildFile << """
-        repositories{
-            maven { url "${anotherRepo.uri}" }
-        }
-        """
-
-        and:
-        withM2(m2)
-
-        when:
-        run 'retrieve'
-
-        then:
-        hasArtifact(moduleA)
-    }
-
-    def hasArtifact(MavenModule module) {
-        def buildDir = file('build')
-        def artifactName = module.artifactFile.name
-        buildDir.assertHasDescendants(artifactName)
-        buildDir.file(artifactName).assertIsCopyOf(module.artifactFile)
-    }
-
-    def withM2(M2Installation m2) {
-        executer.withUserHomeDir(m2.userHomeDir)
-        if (m2.globalMavenDirectory?.exists()) {
-            executer.withEnvironmentVars(M2_HOME:m2.globalMavenDirectory.absolutePath)
-        }
-    }
-
-    M2Installation localM2() {
-        new M2Installation(testDir)
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenParentPomResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenParentPomResolveIntegrationTest.groovy
deleted file mode 100644
index 43d5010..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenParentPomResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.maven
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class MavenParentPomResolveIntegrationTest extends AbstractDependencyResolutionTest {
-
-    def "includes dependencies from parent pom"() {
-        given:
-        server.start()
-
-        def parentDep = mavenRepo().module("org", "parent_dep").publish()
-        def childDep = mavenRepo().module("org", "child_dep").publish()
-
-        def parent = mavenRepo().module("org", "parent")
-        parent.type = 'pom'
-        parent.dependsOn("parent_dep")
-        parent.publish()
-
-        def child = mavenRepo().module("org", "child")
-        child.dependsOn("child_dep")
-        child.parentPomSection = """
-<parent>
-  <groupId>org</groupId>
-  <artifactId>parent</artifactId>
-  <version>1.0</version>
-</parent>
-"""
-        child.publish()
-
-        buildFile << """
-repositories {
-    maven { url 'http://localhost:${server.port}/repo1' }
-}
-configurations { compile }
-dependencies { compile 'org:child:1.0' }
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-
-        when:
-        server.expectGet('/repo1/org/child/1.0/child-1.0.pom', child.pomFile)
-        server.expectGet('/repo1/org/parent/1.0/parent-1.0.pom', parent.pomFile)
-
-        // Will always check for a default artifact with a module with 'pom' packaging
-        server.expectHeadMissing('/repo1/org/parent/1.0/parent-1.0.jar')
-
-        server.expectGet('/repo1/org/child/1.0/child-1.0.jar', child.artifactFile)
-
-        server.expectGet('/repo1/org/parent_dep/1.0/parent_dep-1.0.pom', parentDep.pomFile)
-        server.expectGet('/repo1/org/parent_dep/1.0/parent_dep-1.0.jar', parentDep.artifactFile)
-        server.expectGet('/repo1/org/child_dep/1.0/child_dep-1.0.pom', childDep.pomFile)
-        server.expectGet('/repo1/org/child_dep/1.0/child_dep-1.0.jar', childDep.artifactFile)
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('child-1.0.jar', 'parent_dep-1.0.jar', 'child_dep-1.0.jar')
-    }
-
-    def "looks for parent pom in different repository"() {
-        given:
-        server.start()
-
-        def parent = mavenRepo().module("org", "parent")
-        parent.type = 'pom'
-        parent.publish()
-
-        def child = mavenRepo().module("org", "child")
-        child.parentPomSection = """
-<parent>
-  <groupId>org</groupId>
-  <artifactId>parent</artifactId>
-  <version>1.0</version>
-</parent>
-"""
-        child.publish()
-
-        buildFile << """
-repositories {
-    maven { url 'http://localhost:${server.port}/repo1' }
-    maven { url 'http://localhost:${server.port}/repo2' }
-}
-configurations { compile }
-dependencies { compile 'org:child:1.0' }
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-
-        when:
-        server.expectGet('/repo1/org/child/1.0/child-1.0.pom', child.pomFile)
-        server.expectGet('/repo1/org/child/1.0/child-1.0.jar', child.artifactFile)
-
-        server.expectGetMissing('/repo1/org/parent/1.0/parent-1.0.pom')
-        server.expectHeadMissing('/repo1/org/parent/1.0/parent-1.0.jar')
-        server.expectGet('/repo2/org/parent/1.0/parent-1.0.pom', parent.pomFile)
-        server.expectHead('/repo2/org/parent/1.0/parent-1.0.jar', parent.artifactFile)
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('child-1.0.jar')
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomPackagingResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomPackagingResolveIntegrationTest.groovy
deleted file mode 100644
index 39d24e3..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomPackagingResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.maven
-
-import org.gradle.integtests.fixtures.MavenFileModule
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import spock.lang.FailsWith
-import spock.lang.Issue
-
-class MavenPomPackagingResolveIntegrationTest extends AbstractDependencyResolutionTest {
-    MavenFileModule projectA
-
-    def setup() {
-        server.start()
-
-        projectA = mavenRepo().module('group', 'projectA')
-    }
-
-    private void buildWithDependencies(def dependencies) {
-        buildFile << """
-repositories {
-    maven { url 'http://localhost:${server.port}/repo1' }
-    maven { url 'http://localhost:${server.port}/repo2' }
-}
-configurations { compile }
-dependencies {
-    $dependencies
-}
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-    }
-
-    def "looks for jar artifact for pom with packaging of type 'pom' in the same repository only"() {
-        when:
-        buildWithDependencies("compile 'group:projectA:1.0'")
-        publishWithPackaging('pom')
-
-        and:
-        // First attempts to resolve in repo1
-        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0.pom')
-        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.jar')
-
-        server.expectGet('/repo2/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectHead('/repo2/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-        server.expectGet('/repo2/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.0.jar')
-        def snapshot = file('libs/projectA-1.0.jar').snapshot()
-
-        when:
-        server.resetExpectations()
-        and:
-        run 'retrieve'
-
-        then: // Uses cached artifacts
-        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
-    }
-
-    def "will use jar artifact for pom with packaging that maps to jar"() {
-        when:
-        buildWithDependencies("compile 'group:projectA:1.0'")
-        publishWithPackaging(packaging)
-
-        and:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-
-        then:
-        succeeds 'retrieve'
-
-        and:
-        file('libs').assertHasDescendants('projectA-1.0.jar')
-        file('libs/projectA-1.0.jar').assertIsCopyOf(projectA.artifactFile)
-
-        where:
-        packaging << ['', 'jar', 'eclipse-plugin', 'bundle']
-    }
-
-
-    @Issue('GRADLE-2188')
-    def "will use jar artifact for pom with packaging 'orbit'"() {
-        when:
-        buildWithDependencies("compile 'group:projectA:1.0'")
-        publishWithPackaging('orbit')
-
-        and:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.orbit')
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-
-        then:
-        succeeds 'retrieve'
-
-        and:
-        file('libs').assertHasDescendants('projectA-1.0.jar')
-        file('libs/projectA-1.0.jar').assertIsCopyOf(projectA.artifactFile)
-    }
-
-    @Issue('GRADLE-2188')
-    def "where 'module.custom' exists, will use it as main artifact for pom with packaging 'custom' and emit deprecation warning"() {
-        when:
-        buildWithDependencies("compile 'group:projectA:1.0'")
-        publishWithPackaging('custom', 'custom')
-
-        and:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectHead('/repo1/group/projectA/1.0/projectA-1.0.custom', projectA.artifactFile)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.custom', projectA.artifactFile)
-
-        and:
-        executer.withDeprecationChecksDisabled()
-
-        then:
-        succeeds 'retrieve'
-
-        and:
-        file('libs').assertHasDescendants('projectA-1.0.custom')
-        file('libs/projectA-1.0.custom').assertIsCopyOf(projectA.artifactFile)
-
-        and:
-        result.output.contains("Relying on packaging to define the extension of the main artifact has been deprecated")
-    }
-
-    def "fails and reports type-based location if neither packaging-based or type-based artifact can be located"() {
-        when:
-        buildWithDependencies("compile 'group:projectA:1.0'")
-        publishWithPackaging('custom')
-
-        and:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.custom')
-        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0.jar')
-
-        then:
-        fails 'retrieve'
-
-        and:
-        result.error.contains("Artifact 'group:projectA:1.0 at jar' not found.")
-    }
-
-    def "will use non-jar dependency type to determine jar artifact location"() {
-        when:
-        buildWithDependencies("""
-compile('group:projectA:1.0') {
-    artifact {
-        name = 'projectA'
-        type = 'zip'
-    }
-}
-""")
-        publishWithPackaging('custom')
-
-        and:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-
-        // TODO:GRADLE-2188 This call should not be required, since "type='zip'" on the dependency alleviates the need to check for the packaging artifact
-        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.custom')
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.zip', projectA.artifactFile)
-
-        then:
-        succeeds 'retrieve'
-
-        and:
-        file('libs').assertHasDescendants('projectA-1.0.zip')
-        file('libs/projectA-1.0.zip').assertIsCopyOf(projectA.artifactFile)
-    }
-
-    def "will use non-jar maven dependency type to determine artifact location"() {
-        when:
-        buildWithDependencies("""
-compile 'group:mavenProject:1.0'
-""")
-        def mavenProject = mavenRepo().module('group', 'mavenProject', '1.0').hasType('pom').dependsOn('group', 'projectA', '1.0', 'zip').publish()
-        publishWithPackaging('custom', 'zip')
-
-        and:
-        server.expectGet('/repo1/group/mavenProject/1.0/mavenProject-1.0.pom', mavenProject.pomFile)
-        server.expectHeadMissing('/repo1/group/mavenProject/1.0/mavenProject-1.0.jar')
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-
-        // TODO:GRADLE-2188 This call should not be required, since "type='zip'" on the dependency alleviates the need to check for the packaging artifact
-        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.custom')
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.zip', projectA.artifactFile)
-
-        then:
-        succeeds 'retrieve'
-
-        and:
-        file('libs').assertHasDescendants('projectA-1.0.zip')
-        file('libs/projectA-1.0.zip').assertIsCopyOf(projectA.artifactFile)
-    }
-
-    @FailsWith(value = AssertionError, reason = "Pending better fix for GRADLE-2188")
-    def "does not emit deprecation warning if dependency type is used to locate artifact, even if custom packaging matches file extension"() {
-        when:
-        buildWithDependencies("""
-compile('group:projectA:1.0') {
-    artifact {
-        name = 'projectA'
-        type = 'zip'
-    }
-}
-""")
-        publishWithPackaging('zip')
-
-        and:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.zip', projectA.artifactFile)
-
-        then:
-        succeeds 'retrieve'
-
-        and:
-        file('libs').assertHasDescendants('projectA-1.0.zip')
-        file('libs/projectA-1.0.zip').assertIsCopyOf(projectA.artifactFile)
-
-        and: "Stop the http server here to allow failure to be declared (otherwise occurs in tearDown) - remove this when the test is fixed"
-        server.stop()
-    }
-
-    private def publishWithPackaging(String packaging, String type = 'jar') {
-        projectA.packaging = packaging
-        projectA.type = type
-        projectA.publish()
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotResolveIntegrationTest.groovy
deleted file mode 100644
index 03b6805..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,565 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve.maven
-
-import org.gradle.integtests.fixtures.MavenHttpModule
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class MavenSnapshotResolveIntegrationTest extends AbstractDependencyResolutionTest {
-
-    def "can find and cache snapshots in multiple Maven HTTP repositories"() {
-        server.start()
-        def repo1 = mavenHttpRepo("repo1")
-        def repo2 = mavenHttpRepo("repo2")
-
-        given:
-        buildFile << """
-repositories {
-    maven { url "${repo1.uri}" }
-    maven { url "${repo2.uri}" }
-}
-
-configurations { compile }
-
-dependencies {
-    compile "org.gradle:projectA:1.0-SNAPSHOT"
-    compile "org.gradle:projectB:1.0-SNAPSHOT"
-    compile "org.gradle:nonunique:1.0-SNAPSHOT"
-}
-
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-
-        and: "snapshot modules are published"
-        def repo1ProjectA = repo1.module("org.gradle", "projectA", "1.0-SNAPSHOT").publish()
-        def repo1ProjectB = repo1.module("org.gradle", "projectB", "1.0-SNAPSHOT")
-        def repo2ProjectB = repo2.module("org.gradle", "projectB", "1.0-SNAPSHOT").publish()
-        def repo1NonUnique = repo1.module("org.gradle", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots()
-        def repo2NonUnique = repo2.module("org.gradle", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
-
-        when: "Server provides projectA from repo1"
-        expectModuleServed(repo1ProjectA)
-
-        and: "Server provides projectB from repo2"
-        expectModuleMissing(repo1ProjectB)
-        expectModuleServed(repo2ProjectB)
-
-        and: "Server provides nonunique snapshot from repo2"
-        expectModuleMissing(repo1NonUnique)
-        expectModuleServed(repo2NonUnique)
-
-        and: "We resolve dependencies"
-        run 'retrieve'
-
-        then: "Snapshots are downloaded"
-        file('libs').assertHasDescendants('projectA-1.0-SNAPSHOT.jar', 'projectB-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
-        def snapshotA = file('libs/projectA-1.0-SNAPSHOT.jar').snapshot()
-        def snapshotNonUnique = file('libs/nonunique-1.0-SNAPSHOT.jar').snapshot()
-
-        when: "We resolve with snapshots cached: no server requests"
-        server.resetExpectations()
-        def result = run('retrieve')
-
-        then: "Everything is up to date"
-        result.assertTaskSkipped(':retrieve')
-        file('libs/projectA-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotA);
-        file('libs/nonunique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotNonUnique);
-    }
-
-    def "can find and cache snapshots in Maven HTTP repository with additional artifact urls"() {
-        server.start()
-        def repo1 = mavenHttpRepo("repo1")
-        def repo2 = mavenHttpRepo("repo2")
-
-        given:
-        buildFile << """
-repositories {
-    maven {
-        url "${repo1.uri}"
-        artifactUrls "${repo2.uri}"
-    }
-}
-
-configurations { compile }
-
-dependencies {
-    compile "org.gradle:projectA:1.0-SNAPSHOT"
-    compile "org.gradle:projectB:1.0-SNAPSHOT"
-}
-
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-
-        and: "snapshot modules are published"
-        def projectA = repo1.module("org.gradle", "projectA", "1.0-SNAPSHOT").publish()
-        def repo1ProjectB = repo1.module("org.gradle", "projectB", "1.0-SNAPSHOT").publish()
-        def repo2ProjectB = repo2.module("org.gradle", "projectB", "1.0-SNAPSHOT").publish()
-
-        when: "Server provides projectA from repo1"
-        expectModuleServed(projectA)
-
-        and: "Server provides projectB with artifact in repo2"
-        repo1ProjectB.expectMetaDataGet()
-        repo1ProjectB.expectPomGet()
-        // TODO - should fetch this once
-        repo1ProjectB.expectMetaDataGet()
-
-        server.expectGetMissing("/repo1/org/gradle/projectB/1.0-SNAPSHOT/${repo1ProjectB.artifactFile.name}")
-        server.expectGetMissing("/repo1/org/gradle/projectB/1.0-SNAPSHOT/projectB-1.0-SNAPSHOT.jar")
-
-        // TODO: This is not correct - should be looking for jar with unique version to fetch snapshot
-        server.expectGet("/repo2/org/gradle/projectB/1.0-SNAPSHOT/projectB-1.0-SNAPSHOT.jar", repo2ProjectB.artifactFile)
-//        server.expectGet("/repo2/org/gradle/projectB/1.0-SNAPSHOT/${repo2ProjectB.artifactFile.name}", repo2ProjectB.artifactFile)
-
-        and: "We resolve dependencies"
-        run 'retrieve'
-
-        then: "Snapshots are downloaded"
-        file('libs').assertHasDescendants('projectA-1.0-SNAPSHOT.jar', 'projectB-1.0-SNAPSHOT.jar')
-        def snapshotA = file('libs/projectA-1.0-SNAPSHOT.jar').snapshot()
-        def snapshotB = file('libs/projectB-1.0-SNAPSHOT.jar').snapshot()
-
-        when: "We resolve with snapshots cached: no server requests"
-        server.resetExpectations()
-        def result = run('retrieve')
-
-        then: "Everything is up to date"
-        result.assertTaskSkipped(':retrieve')
-        file('libs/projectA-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotA);
-        file('libs/projectB-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotB);
-    }
-
-    def "will detect changed snapshot artifacts when pom has not changed"() {
-        server.start()
-
-        buildFile << """
-repositories {
-    maven { url "${mavenHttpRepo.uri}" }
-}
-
-configurations { compile }
-configurations.compile.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-
-dependencies { 
-    compile "org.gradle:unique:1.0-SNAPSHOT" 
-    compile "org.gradle:nonunique:1.0-SNAPSHOT" 
-}
-
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-
-        when: "snapshot modules are published"
-        def uniqueVersionModule = mavenHttpRepo.module("org.gradle", "unique", "1.0-SNAPSHOT").publish()
-        def nonUniqueVersionModule = mavenHttpRepo.module("org.gradle", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
-
-        and: "Server handles requests"
-        expectModuleServed(uniqueVersionModule)
-        expectModuleServed(nonUniqueVersionModule)
-
-        and: "We resolve dependencies"
-        run 'retrieve'
-
-        then: "Snapshots are downloaded"
-        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
-        def uniqueJarSnapshot = file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).snapshot()
-        def nonUniqueJarSnapshot = file('libs/nonunique-1.0-SNAPSHOT.jar').assertIsCopyOf(nonUniqueVersionModule.artifactFile).snapshot()
-        server.resetExpectations()
-
-        when: "Change the snapshot artifacts directly: do not change the pom"
-        uniqueVersionModule.artifactFile << 'more content'
-        nonUniqueVersionModule.artifactFile << 'more content'
-
-        and: "No server requests"
-        expectChangedArtifactServed(uniqueVersionModule)
-        expectChangedArtifactServed(nonUniqueVersionModule)
-
-        and: "Resolve dependencies again"
-        run 'retrieve'
-
-        then:
-        file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).assertHasChangedSince(uniqueJarSnapshot)
-        file('libs/nonunique-1.0-SNAPSHOT.jar').assertIsCopyOf(nonUniqueVersionModule.artifactFile).assertHasChangedSince(nonUniqueJarSnapshot)
-    }
-
-    def "uses cached snapshots from a Maven HTTP repository until the snapshot timeout is reached"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    maven { url "${mavenHttpRepo.uri}" }
-}
-
-configurations { compile }
-
-if (project.hasProperty('noTimeout')) {
-    configurations.all {
-        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-    }
-}
-
-dependencies {
-    compile "org.gradle:unique:1.0-SNAPSHOT"
-    compile "org.gradle:nonunique:1.0-SNAPSHOT"
-}
-
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-
-        when: "snapshot modules are published"
-        def uniqueVersionModule = mavenHttpRepo.module("org.gradle", "unique", "1.0-SNAPSHOT").publish()
-        def nonUniqueVersionModule = mavenHttpRepo.module("org.gradle", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
-
-        and: "Server handles requests"
-        expectModuleServed(uniqueVersionModule)
-        expectModuleServed(nonUniqueVersionModule)
-
-        and: "We resolve dependencies"
-        run 'retrieve'
-
-        then: "Snapshots are downloaded"
-        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
-        def uniqueJarSnapshot = file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).snapshot()
-        def nonUniqueJarSnapshot = file('libs/nonunique-1.0-SNAPSHOT.jar').assertIsCopyOf(nonUniqueVersionModule.artifactFile).snapshot()
-
-        when: "Republish the snapshots"
-        uniqueVersionModule.publishWithChangedContent()
-        nonUniqueVersionModule.publishWithChangedContent()
-
-        and: "No server requests"
-        server.resetExpectations()
-
-        and: "Resolve dependencies again, with cached versions"
-        run 'retrieve'
-
-        then:
-        file('libs/unique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(uniqueJarSnapshot)
-        file('libs/nonunique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(nonUniqueJarSnapshot)
-
-        when: "Server handles requests"
-        expectChangedModuleServed(uniqueVersionModule)
-        expectChangedModuleServed(nonUniqueVersionModule)
-
-        and: "Resolve dependencies with cache expired"
-        executer.withArguments("-PnoTimeout")
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
-        file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).assertHasChangedSince(uniqueJarSnapshot)
-        file('libs/nonunique-1.0-SNAPSHOT.jar').assertIsCopyOf(nonUniqueVersionModule.artifactFile).assertHasChangedSince(nonUniqueJarSnapshot);
-    }
-
-    def "does not download snapshot artifacts after expiry when snapshot has not changed"() {
-        server.start()
-
-        buildFile << """
-repositories {
-    maven { url "${mavenHttpRepo.uri}" }
-}
-
-configurations { compile }
-
-configurations.all {
-    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-}
-
-dependencies {
-    compile "org.gradle:testproject:1.0-SNAPSHOT"
-}
-
-task retrieve(type: Sync) {
-    into 'build'
-    from configurations.compile
-}
-"""
-
-        when: "Publish the first snapshot"
-        def module = mavenHttpRepo.module("org.gradle", "testproject", "1.0-SNAPSHOT").publish()
-
-        and: "Server handles requests"
-        expectModuleServed(module)
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
-        def snapshot = file('build/testproject-1.0-SNAPSHOT.jar').assertIsCopyOf(module.artifactFile).snapshot()
-
-        when: "Server handles requests"
-        server.resetExpectations()
-        expectChangedProbe(module)
-
-        // Retrieve again with zero timeout should check for updated snapshot
-        and:
-        def result = run 'retrieve'
-
-        then:
-        result.assertTaskSkipped(':retrieve')
-        file('build/testproject-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshot);
-    }
-
-    def "does not download snapshot artifacts more than once per build"() {
-        server.start()
-        given:
-        def module = mavenHttpRepo.module("org.gradle", "testproject", "1.0-SNAPSHOT").publish()
-
-        and:
-        settingsFile << "include 'a', 'b'"
-        buildFile << """
-allprojects {
-    repositories {
-        maven { url "${mavenHttpRepo.uri}" }
-    }
-
-    configurations { compile }
-
-    configurations.all {
-        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-    }
-
-    dependencies {
-        compile "org.gradle:testproject:1.0-SNAPSHOT"
-    }
-
-    task retrieve(type: Sync) {
-        into 'build'
-        from configurations.compile
-    }
-}
-"""
-        when: "Module is requested once"
-        expectModuleServed(module)
-
-        then:
-        run 'retrieve'
-
-        and:
-        file('build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
-        file('a/build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
-        file('b/build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
-    }
-
-    def "can update snapshot artifact during build even if it is locked earlier in build"() {
-        server.start()
-        given:
-        def module = mavenHttpRepo("/repo", maven("repo1")).module("org.gradle", "testproject", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
-        def module2 = mavenHttpRepo("/repo", maven("repo2")).module("org.gradle", "testproject", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
-        module2.artifactFile << module2.artifactFile.bytes // ensure it's a different length to the first one
-        module2.pomFile << '    ' // ensure it's a different length to the first one
-
-        and:
-        settingsFile << "include 'first', 'second'"
-        buildFile << """
-def fileLocks = [:]
-subprojects {
-    repositories {
-        maven { url "http://localhost:${server.port}/repo" }
-    }
-
-    configurations { compile }
-
-    configurations.all {
-        resolutionStrategy.resolutionRules.eachModule({ module ->
-            module.refresh()
-        } as Action)
-    }
-
-    dependencies {
-        compile "org.gradle:testproject:1.0-SNAPSHOT"
-    }
-
-    task lock << {
-        configurations.compile.each { file ->
-            println "locking " + file
-            def lockFile = new RandomAccessFile(file.canonicalPath, 'r')
-            fileLocks[file] = lockFile
-        }
-    }
-
-    task retrieve(type: Sync) {
-        into 'build'
-        from configurations.compile
-    }
-    retrieve.dependsOn 'lock'
-}
-project('second') {
-    lock.dependsOn ':first:lock'
-    retrieve.dependsOn ':first:retrieve'
-
-    task cleanup << {
-        fileLocks.each { key, value ->
-            println "unlocking " + key
-            value.close()
-        }
-    }
-    cleanup.dependsOn 'retrieve'
-}
-"""
-        when: "Module is requested once"
-        module.expectMetaDataGet()
-        module.expectPomGet()
-        module.expectMetaDataGet()
-        module.expectArtifactGet()
-
-        module2.expectMetaDataGet()
-        module2.expectPomHead()
-        module2.expectPomSha1Get()
-        module2.expectPomGet()
-        module2.expectMetaDataGet()
-        module2.expectArtifactHead()
-        module2.expectArtifactSha1Get()
-        module2.expectArtifactGet()
-
-        then:
-        run 'cleanup'
-
-        and:
-        file('first/build/testproject-1.0-SNAPSHOT.jar').assertIsCopyOf(module.artifactFile)
-        file('second/build/testproject-1.0-SNAPSHOT.jar').assertIsCopyOf(module2.artifactFile)
-    }
-
-    def "avoid redownload unchanged artifact when no checksum available"() {
-        server.start()
-
-        given:
-        buildFile << """
-            repositories {
-                maven { url "${mavenHttpRepo.uri}" }
-            }
-
-            configurations { compile }
-
-            configurations.all {
-                resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-            }
-
-            dependencies {
-                compile group: "group", name: "projectA", version: "1.1-SNAPSHOT"
-            }
-
-            task retrieve(type: Copy) {
-                into 'build'
-                from configurations.compile
-            }
-        """
-
-        and:
-        def module = mavenHttpRepo.module("group", "projectA", "1.1-SNAPSHOT").withNonUniqueSnapshots().publish()
-        // Set the last modified to something that's not going to be anything “else”.
-        // There are lots of dates floating around in a resolution and we want to make
-        // sure we use this.
-        module.artifactFile.setLastModified(2000)
-        module.pomFile.setLastModified(6000)
-
-        when:
-        expectModuleServed(module)
-
-        run "retrieve"
-
-        then:
-        def downloadedJarFile = file("build/projectA-1.1-SNAPSHOT.jar")
-        downloadedJarFile.assertIsCopyOf(module.artifactFile)
-        def initialDownloadJarFileSnapshot = downloadedJarFile.snapshot()
-
-        when:
-        server.resetExpectations()
-        expectChangedProbe(module)
-
-        run "retrieve"
-
-        then:
-        downloadedJarFile.assertHasNotChangedSince(initialDownloadJarFileSnapshot)
-
-        when:
-        module.publishWithChangedContent()
-        server.resetExpectations()
-        module.expectMetaDataGet()
-        module.expectPomHead()
-        module.expectPomSha1GetMissing()
-        module.expectPomGet()
-        // TODO - should only ask for metadata once
-        module.expectMetaDataGet()
-        module.expectArtifactHead()
-        module.expectArtifactSha1GetMissing()
-        module.expectArtifactGet()
-
-        run "retrieve"
-
-        then:
-        downloadedJarFile.assertHasChangedSince(initialDownloadJarFileSnapshot)
-        downloadedJarFile.assertIsCopyOf(module.artifactFile)
-    }
-
-    private expectModuleServed(MavenHttpModule module) {
-        module.expectMetaDataGet()
-        module.expectPomGet()
-        // TODO - should only ask for metadata once
-        module.expectMetaDataGet()
-        module.expectArtifactGet()
-    }
-
-    private expectChangedModuleServed(MavenHttpModule module) {
-        module.expectMetaDataGet()
-        module.expectPomHead()
-        module.expectPomSha1Get()
-        module.expectPomGet()
-        // TODO - should only ask for metadata once
-        module.expectMetaDataGet()
-        module.expectArtifactHead()
-        module.expectArtifactSha1Get()
-        module.expectArtifactGet()
-    }
-
-    private expectChangedArtifactServed(MavenHttpModule module) {
-        module.expectMetaDataGet()
-        module.expectPomHead()
-        // TODO - should only ask for metadata once
-        module.expectMetaDataGet()
-        module.expectArtifactHead()
-        module.expectArtifactSha1Get()
-        module.expectArtifactGet()
-    }
-
-    private expectChangedProbe(MavenHttpModule module) {
-        module.expectMetaDataGet()
-        module.expectPomHead()
-        // TODO - should only ask for metadata once
-        module.expectMetaDataGet()
-        module.expectArtifactHead()
-    }
-    
-    private expectModuleMissing(MavenHttpModule module) {
-        module.expectMetaDataGetMissing()
-        module.expectPomGetMissing()
-        // TODO - should only ask for metadata once
-        module.expectMetaDataGetMissing()
-        module.expectArtifactHeadMissing()
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAnnounceIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAnnounceIntegrationTest.groovy
index 1f7762b..8aa2300 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAnnounceIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAnnounceIntegrationTest.groovy
@@ -16,15 +16,12 @@
 
 package org.gradle.integtests.samples
 
-import org.junit.Rule
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.Sample
-import spock.lang.Specification
+import org.junit.Rule
+
+class SamplesAnnounceIntegrationTest extends AbstractIntegrationSpec {
 
-class SamplesAnnounceIntegrationTest extends Specification {
-    @Rule GradleDistribution dist
-    @Rule GradleDistributionExecuter executer
     @Rule Sample sample = new Sample("announce")
 
     def "make some announcements"() {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAntlrIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAntlrIntegrationTest.groovy
index 97dedec..253723d 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAntlrIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAntlrIntegrationTest.groovy
@@ -15,17 +15,15 @@
  */
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
 
-class SamplesAntlrIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class SamplesAntlrIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final Sample sample = new Sample('antlr')
 
     @Test
@@ -36,7 +34,7 @@ class SamplesAntlrIntegrationTest {
         executer.inDirectory(projectDir).withTasks('clean', 'build').withArguments("--no-opt").run()
 
         // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(projectDir)
         result.assertTestClassesExecuted('org.gradle.GrammarTest')
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesApplicationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesApplicationIntegrationTest.groovy
index 61c1150..51fae82 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesApplicationIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesApplicationIntegrationTest.groovy
@@ -15,14 +15,14 @@
  */
 package org.gradle.integtests.samples
 
-import org.gradle.util.TestFile
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.integtests.fixtures.ScriptExecuter
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
-import spock.lang.Specification
-import org.gradle.integtests.fixtures.*
 
-class SamplesApplicationIntegrationTest extends Specification {
-    @Rule GradleDistribution distribution = new GradleDistribution()
-    @Rule GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class SamplesApplicationIntegrationTest extends AbstractIntegrationSpec {
+
     @Rule Sample sample = new Sample('application')
 
     def canRunTheApplicationUsingRunTask() {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesClientModuleDependenciesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesClientModuleDependenciesIntegrationTest.groovy
new file mode 100644
index 0000000..b5191fa
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesClientModuleDependenciesIntegrationTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.samples
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.Sample
+import org.junit.Rule
+
+class SamplesClientModuleDependenciesIntegrationTest extends AbstractIntegrationSpec {
+    @Rule Sample sample = new Sample("clientModuleDependencies")
+
+    def "resolve shared"() {
+        inDirectory(sample.dir.file("shared"))
+
+        expect:
+        // the actual testing is done in the build script
+        succeeds("testDeps")
+    }
+
+    def "resolve api"() {
+        inDirectory(sample.dir.file("api"))
+
+        expect:
+        // the actual testing is done in the build script
+        succeeds("testDeps")
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCodeQualityIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCodeQualityIntegrationTest.groovy
index 00947a4..086748d 100755
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCodeQualityIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCodeQualityIntegrationTest.groovy
@@ -15,19 +15,17 @@
  */
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
 
 /**
  * @author Hans Dockter
  */
-class SamplesCodeQualityIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class SamplesCodeQualityIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final Sample sample = new Sample('codeQuality')
 
     @Test
@@ -35,7 +33,7 @@ class SamplesCodeQualityIntegrationTest {
         TestFile projectDir = sample.dir
         TestFile buildDir = projectDir.file('build')
 
-        executer.inDirectory(projectDir).withForkingExecuter().withTasks('check').run()
+        executer.inDirectory(projectDir).requireGradleHome(true).withTasks('check').run()
 
         buildDir.file('reports/checkstyle/main.xml').assertIsFile()
         buildDir.file('reports/codenarc/main.html').assertIsFile()
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomBuildLanguageIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomBuildLanguageIntegrationTest.groovy
index 5bb4318..77b831b 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomBuildLanguageIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomBuildLanguageIntegrationTest.groovy
@@ -15,16 +15,14 @@
  */
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
 
-class SamplesCustomBuildLanguageIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class SamplesCustomBuildLanguageIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final Sample sample = new Sample('customBuildLanguage')
 
     @Test
@@ -32,7 +30,7 @@ class SamplesCustomBuildLanguageIntegrationTest {
         TestFile rootDir = sample.dir
         executer.inDirectory(rootDir).withTasks('clean', 'dist').run()
 
-        TestFile expandDir = dist.testFile('expand-basic')
+        TestFile expandDir = file('expand-basic')
         rootDir.file('basicEdition/build/distributions/some-company-basic-edition-1.0.zip').unzipTo(expandDir)
         expandDir.assertHasDescendants(
                 'readme.txt',
@@ -43,7 +41,7 @@ class SamplesCustomBuildLanguageIntegrationTest {
                 'lib/commons-lang-2.4.jar'
         )
 
-        expandDir = dist.testFile('expand-enterprise')
+        expandDir = file('expand-enterprise')
         rootDir.file('enterpriseEdition/build/distributions/some-company-enterprise-edition-1.0.zip').unzipTo(expandDir)
         expandDir.assertHasDescendants(
                 'readme.txt',
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomPluginIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomPluginIntegrationTest.groovy
index 63a453b..2a86406 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomPluginIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomPluginIntegrationTest.groovy
@@ -15,9 +15,9 @@
  */
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
-import org.gradle.integtests.fixtures.Sample
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
 import org.junit.Rule
 
 class SamplesCustomPluginIntegrationTest extends AbstractIntegrationSpec {
@@ -36,7 +36,7 @@ class SamplesCustomPluginIntegrationTest extends AbstractIntegrationSpec {
         executer.inDirectory(producerDir).withTasks('check').run()
 
         then:
-        def result = new JUnitTestExecutionResult(producerDir)
+        def result = new DefaultTestExecutionResult(producerDir)
         result.assertTestClassesExecuted('org.gradle.GreetingTaskTest', 'org.gradle.GreetingPluginTest')
     }
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesExcludesAndClassifiersIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesExcludesAndClassifiersIntegrationTest.groovy
index 4879df5..0d9b4ce 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesExcludesAndClassifiersIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesExcludesAndClassifiersIntegrationTest.groovy
@@ -16,11 +16,11 @@
 
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.Sample
 import org.junit.Rule
 import org.junit.Test
+
 import static org.hamcrest.Matchers.containsString
 import static org.hamcrest.Matchers.not
 import static org.junit.Assert.assertThat
@@ -28,9 +28,8 @@ import static org.junit.Assert.assertThat
 /**
  * @author Hans Dockter
  */
-class SamplesExcludesAndClassifiersIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class SamplesExcludesAndClassifiersIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final Sample sample = new Sample('userguide/artifacts/excludesAndClassifiers')
 
     @Test
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyCustomizedLayoutIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyCustomizedLayoutIntegrationTest.groovy
index 0f25636..f8ec1c4 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyCustomizedLayoutIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyCustomizedLayoutIntegrationTest.groovy
@@ -16,17 +16,15 @@
 
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
 
-class SamplesGroovyCustomizedLayoutIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class SamplesGroovyCustomizedLayoutIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final Sample sample = new Sample('groovy/customizedLayout')
 
     @Test
@@ -35,11 +33,11 @@ class SamplesGroovyCustomizedLayoutIntegrationTest {
         executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
 
         // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(groovyProjectDir)
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(groovyProjectDir)
         result.assertTestClassesExecuted('org.gradle.PersonTest')
 
         // Check contents of jar
-        TestFile tmpDir = dist.testDir.file('jarContents')
+        TestFile tmpDir = file('jarContents')
         groovyProjectDir.file('build/libs/customizedLayout.jar').unzipTo(tmpDir)
         tmpDir.assertHasDescendants(
                 'META-INF/MANIFEST.MF',
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyMultiProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyMultiProjectIntegrationTest.groovy
index 1842034..cd49402 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyMultiProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyMultiProjectIntegrationTest.groovy
@@ -16,22 +16,20 @@
 
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
+
 import static org.hamcrest.Matchers.containsString
 
 /**
  * @author Hans Dockter
  */
-class SamplesGroovyMultiProjectIntegrationTest {
+class SamplesGroovyMultiProjectIntegrationTest extends AbstractIntegrationTest {
     static final String TEST_PROJECT_NAME = 'testproject'
 
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
     @Rule public final Sample sample = new Sample('groovy/multiproject')
 
     private List mainFiles = ['JavaPerson', 'GroovyPerson', 'GroovyJavaPerson']
@@ -58,7 +56,7 @@ class SamplesGroovyMultiProjectIntegrationTest {
         testFiles.each { testProjectDir.file('build', it).assertIsFile() }
 
         // Check contents of jar
-        TestFile tmpDir = dist.testDir.file('jarContents')
+        TestFile tmpDir = file('jarContents')
         testProjectDir.file("build/libs/$TEST_PROJECT_NAME-1.0.jar").unzipTo(tmpDir)
         tmpDir.assertHasDescendants(
                 'META-INF/MANIFEST.MF',
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyQuickstartIntegrationTest.groovy
index 81e8ede..933d789 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyQuickstartIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyQuickstartIntegrationTest.groovy
@@ -16,17 +16,15 @@
 
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
 
-class SamplesGroovyQuickstartIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class SamplesGroovyQuickstartIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final Sample sample = new Sample('groovy/quickstart')
 
     @Test
@@ -35,11 +33,11 @@ class SamplesGroovyQuickstartIntegrationTest {
         executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
 
         // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(groovyProjectDir)
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(groovyProjectDir)
         result.assertTestClassesExecuted('org.gradle.PersonTest')
 
         // Check contents of jar
-        TestFile tmpDir = dist.testDir.file('jarContents')
+        TestFile tmpDir = file('jarContents')
         groovyProjectDir.file('build/libs/quickstart.jar').unzipTo(tmpDir)
         tmpDir.assertHasDescendants(
                 'META-INF/MANIFEST.MF',
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaApiAndImplIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaApiAndImplIntegrationTest.groovy
index 30ca1bb..f3bed10 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaApiAndImplIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaApiAndImplIntegrationTest.groovy
@@ -75,7 +75,7 @@ class SamplesJavaApiAndImplIntegrationTest extends AbstractIntegrationSpec {
     }
 
     def pom(suffix) {
-        module(suffix).pom
+        module(suffix).parsedPom
     }
 
     def module(suffix) {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaBaseIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaBaseIntegrationTest.groovy
index 22eee53..b26a29d 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaBaseIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaBaseIntegrationTest.groovy
@@ -16,11 +16,10 @@
 
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
 
@@ -28,9 +27,8 @@ import org.junit.Test
  * @author Hans Dockter
  */
 
-class SamplesJavaBaseIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class SamplesJavaBaseIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final Sample sample = new Sample('java/base')
 
     @Test
@@ -41,14 +39,14 @@ class SamplesJavaBaseIntegrationTest {
         executer.inDirectory(javaprojectDir).withTasks('clean', 'build').run()
 
         // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir.file('test'))
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(javaprojectDir.file('test'))
         result.assertTestClassesExecuted('org.gradle.PersonTest')
 
         // Check jar exists
         javaprojectDir.file("prod/build/libs/prod-1.0.jar").assertIsFile()
         
         // Check contents of Jar
-        TestFile jarContents = dist.testDir.file('jar')
+        TestFile jarContents = file('jar')
         javaprojectDir.file('prod/build/libs/prod-1.0.jar').unzipTo(jarContents)
         jarContents.assertHasDescendants(
                 'META-INF/MANIFEST.MF',
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaCustomizedLayoutIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaCustomizedLayoutIntegrationTest.groovy
index 5e3e3b4..ad35772 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaCustomizedLayoutIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaCustomizedLayoutIntegrationTest.groovy
@@ -16,11 +16,10 @@
 
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
 
@@ -28,9 +27,8 @@ import org.junit.Test
  * @author Hans Dockter
  */
 
-class SamplesJavaCustomizedLayoutIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class SamplesJavaCustomizedLayoutIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final Sample sample = new Sample('java/customizedLayout')
 
     @Test
@@ -41,14 +39,14 @@ class SamplesJavaCustomizedLayoutIntegrationTest {
         executer.inDirectory(javaprojectDir).withTasks('clean', 'build', 'uploadArchives').run()
 
         // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir)
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(javaprojectDir)
         result.assertTestClassesExecuted('org.gradle.PersonTest')
 
         // Check jar exists
         javaprojectDir.file('build/libs/customizedLayout.jar').assertIsFile()
 
         // Check contents of Jar
-        TestFile jarContents = dist.testDir.file('jar')
+        TestFile jarContents = file('jar')
         javaprojectDir.file('build/libs/customizedLayout.jar').unzipTo(jarContents)
         jarContents.assertHasDescendants(
                 'META-INF/MANIFEST.MF',
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaMultiProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaMultiProjectIntegrationTest.groovy
index a38a4e8..7b828cb 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaMultiProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaMultiProjectIntegrationTest.groovy
@@ -18,20 +18,20 @@
 
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+
 import static org.hamcrest.Matchers.containsString
 
 /**
  * @author Hans Dockter
  */
-class SamplesJavaMultiProjectIntegrationTest {
+class SamplesJavaMultiProjectIntegrationTest extends AbstractIntegrationTest {
 
     static final String JAVA_PROJECT_NAME = 'java/multiproject'
     static final String SHARED_NAME = 'shared'
@@ -43,8 +43,6 @@ class SamplesJavaMultiProjectIntegrationTest {
     private TestFile javaprojectDir
     private List projects;
 
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
     @Rule public final Sample sample = new Sample('java/multiproject')
 
     @Before
@@ -66,7 +64,7 @@ class SamplesJavaMultiProjectIntegrationTest {
         TestFile buildSrcDir = javaprojectDir.file('buildSrc')
 
         buildSrcDir.file('build/libs/buildSrc.jar').assertIsFile()
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(buildSrcDir)
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(buildSrcDir)
         result.assertTestClassesExecuted('org.gradle.buildsrc.BuildSrcClassTest')
     }
 
@@ -87,14 +85,14 @@ class SamplesJavaMultiProjectIntegrationTest {
         assertExists(javaprojectDir, WEBAPP_PATH, packagePrefix, WEBAPP_NAME, 'TestTest.class')
 
         // Check test results and report
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir.file(SHARED_NAME))
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(javaprojectDir.file(SHARED_NAME))
         result.assertTestClassesExecuted('org.gradle.shared.PersonTest')
 
-        result = new JUnitTestExecutionResult(javaprojectDir.file(WEBAPP_PATH))
+        result = new DefaultTestExecutionResult(javaprojectDir.file(WEBAPP_PATH))
         result.assertTestClassesExecuted('org.gradle.webservice.TestTestTest')
 
         // Check contents of shared jar
-        TestFile tmpDir = dist.testDir.file("$SHARED_NAME-1.0.jar")
+        TestFile tmpDir = file("$SHARED_NAME-1.0.jar")
         javaprojectDir.file(SHARED_NAME, "build/libs/$SHARED_NAME-1.0.jar").unzipTo(tmpDir)
         tmpDir.assertHasDescendants(
                 'META-INF/MANIFEST.MF',
@@ -105,7 +103,7 @@ class SamplesJavaMultiProjectIntegrationTest {
         )
 
         // Check contents of API jar
-        tmpDir = dist.testDir.file("$API_NAME-1.0.jar")
+        tmpDir = file("$API_NAME-1.0.jar")
         javaprojectDir.file(API_NAME, "build/libs/$API_NAME-1.0.jar").unzipTo(tmpDir)
         tmpDir.assertHasDescendants(
                 'META-INF/MANIFEST.MF',
@@ -113,14 +111,14 @@ class SamplesJavaMultiProjectIntegrationTest {
                 'org/gradle/apiImpl/Impl.class')
 
         // Check contents of API jar
-        tmpDir = dist.testDir.file("$API_NAME-spi-1.0.jar")
+        tmpDir = file("$API_NAME-spi-1.0.jar")
         javaprojectDir.file(API_NAME, "build/libs/$API_NAME-spi-1.0.jar").unzipTo(tmpDir)
         tmpDir.assertHasDescendants(
                 'META-INF/MANIFEST.MF',
                 'org/gradle/api/PersonList.class')
 
         // Check contents of War
-        tmpDir = dist.testDir.file("$WEBAPP_NAME-2.5.war")
+        tmpDir = file("$WEBAPP_NAME-2.5.war")
         javaprojectDir.file(WEBAPP_PATH, "build/libs/$WEBAPP_NAME-2.5.war").unzipTo(tmpDir)
         tmpDir.assertHasDescendants(
                 'META-INF/MANIFEST.MF',
@@ -134,7 +132,7 @@ class SamplesJavaMultiProjectIntegrationTest {
         )
 
         // Check contents of dist zip
-        tmpDir = dist.testDir.file("$API_NAME-1.0.zip")
+        tmpDir = file("$API_NAME-1.0.zip")
         javaprojectDir.file(API_NAME, "build/distributions/$API_NAME-1.0.zip").unzipTo(tmpDir)
         tmpDir.assertHasDescendants(
                 'README.txt',
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaOnlyIfIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaOnlyIfIntegrationTest.groovy
index 4ace76d..091578f 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaOnlyIfIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaOnlyIfIntegrationTest.groovy
@@ -17,16 +17,14 @@
 
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
 
-public class SamplesJavaOnlyIfIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+public class SamplesJavaOnlyIfIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final Sample sample = new Sample('java/onlyif')
 
     /**
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaProjectWithIntTestsIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaProjectWithIntTestsIntegrationTest.groovy
index a8ac3b2..a3e4f68 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaProjectWithIntTestsIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaProjectWithIntTestsIntegrationTest.groovy
@@ -16,20 +16,18 @@
 
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
 
 /**
  * @author Hans Dockter
  */
-class SamplesJavaProjectWithIntTestsIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class SamplesJavaProjectWithIntTestsIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final Sample sample = new Sample('java/withIntegrationTests')
 
     @Test
@@ -40,7 +38,7 @@ class SamplesJavaProjectWithIntTestsIntegrationTest {
         executer.inDirectory(javaprojectDir).withTasks('clean', 'integrationTest').run()
 
         // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir)
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(javaprojectDir)
         result.assertTestClassesExecuted('org.gradle.PersonIntegrationTest')
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaQuickstartIntegrationTest.groovy
index 4138243..631cbc1 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaQuickstartIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaQuickstartIntegrationTest.groovy
@@ -16,23 +16,23 @@
 
 package org.gradle.integtests.samples
 
-import java.util.jar.Manifest
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
+
+import java.util.jar.Manifest
+
 import static org.hamcrest.Matchers.equalTo
 import static org.junit.Assert.assertThat
 
 /**
  * @author Hans Dockter
  */
-class SamplesJavaQuickstartIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class SamplesJavaQuickstartIntegrationTest extends  AbstractIntegrationTest {
+
     @Rule public final Sample sample = new Sample('java/quickstart')
 
     @Test
@@ -43,7 +43,7 @@ class SamplesJavaQuickstartIntegrationTest {
         executer.inDirectory(javaprojectDir).withTasks('clean', 'build', 'uploadArchives').run()
 
         // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir)
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(javaprojectDir)
         result.assertTestClassesExecuted('org.gradle.PersonTest')
 
         // Check jar exists
@@ -53,7 +53,7 @@ class SamplesJavaQuickstartIntegrationTest {
         javaprojectDir.file('repos/quickstart-1.0.jar').assertIsFile()
 
         // Check contents of Jar
-        TestFile jarContents = dist.testDir.file('jar')
+        TestFile jarContents = file('jar')
         javaprojectDir.file('repos/quickstart-1.0.jar').unzipTo(jarContents)
         jarContents.assertHasDescendants(
                 'META-INF/MANIFEST.MF',
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndGroovyIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndGroovyIntegrationTest.groovy
index 4f1e060..e81d5b7 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndGroovyIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndGroovyIntegrationTest.groovy
@@ -16,18 +16,17 @@
 
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
+
 import static org.hamcrest.Matchers.containsString
 
-class SamplesMixedJavaAndGroovyIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class SamplesMixedJavaAndGroovyIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final Sample sample = new Sample('groovy/mixedJavaAndGroovy')
 
     @Test
@@ -36,11 +35,11 @@ class SamplesMixedJavaAndGroovyIntegrationTest {
         executer.inDirectory(projectDir).withTasks('clean', 'build').run()
 
         // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(projectDir)
         result.assertTestClassesExecuted('org.gradle.PersonTest')
 
         // Check contents of jar
-        TestFile tmpDir = dist.testDir.file('jarContents')
+        TestFile tmpDir = file('jarContents')
         projectDir.file('build/libs/mixedJavaAndGroovy-1.0.jar').unzipTo(tmpDir)
         tmpDir.assertHasDescendants(
                 'META-INF/MANIFEST.MF',
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndScalaIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndScalaIntegrationTest.groovy
index da5fabf..a343694 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndScalaIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndScalaIntegrationTest.groovy
@@ -16,18 +16,17 @@
 
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
+
 import static org.hamcrest.Matchers.containsString
 
-class SamplesMixedJavaAndScalaIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class SamplesMixedJavaAndScalaIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final Sample sample = new Sample('scala/mixedJavaAndScala')
 
     @Test
@@ -38,11 +37,11 @@ class SamplesMixedJavaAndScalaIntegrationTest {
         executer.inDirectory(projectDir).withTasks('clean', 'build').run()
 
         // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(projectDir)
         result.assertTestClassesExecuted('org.gradle.sample.PersonTest')
 
         // Check contents of Jar
-        TestFile jarContents = dist.testDir.file('jar')
+        TestFile jarContents = file('jar')
         projectDir.file("build/libs/mixedJavaAndScala-1.0.jar").unzipTo(jarContents)
         jarContents.assertHasDescendants(
                 'META-INF/MANIFEST.MF',
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesRepositoriesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesRepositoriesIntegrationTest.groovy
index f008756..05371b8 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesRepositoriesIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesRepositoriesIntegrationTest.groovy
@@ -16,20 +16,19 @@
  
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.Sample
 import org.junit.Rule
 import org.junit.Test
+
 import static org.hamcrest.Matchers.equalTo
 import static org.junit.Assert.assertThat
 
 /**
  * @author Hans Dockter
  */
-class SamplesRepositoriesIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class SamplesRepositoriesIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final Sample sample = new Sample('userguide/artifacts/defineRepository')
 
     @Test
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaCustomizedLayoutIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaCustomizedLayoutIntegrationTest.groovy
index 7d9b41f..fd64a47 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaCustomizedLayoutIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaCustomizedLayoutIntegrationTest.groovy
@@ -16,17 +16,15 @@
 
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import org.junit.Test
 
-class SamplesScalaCustomizedLayoutIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class SamplesScalaCustomizedLayoutIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final Sample sample = new Sample('scala/customizedLayout')
 
     @Test
@@ -37,11 +35,11 @@ class SamplesScalaCustomizedLayoutIntegrationTest {
         executer.inDirectory(projectDir).withTasks('clean', 'build').run()
 
         // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(projectDir)
         result.assertTestClassesExecuted('org.gradle.sample.impl.PersonImplTest')
 
         // Check contents of Jar
-        TestFile jarContents = dist.testDir.file('jar')
+        TestFile jarContents = file('jar')
         projectDir.file("build/libs/customizedLayout.jar").unzipTo(jarContents)
         jarContents.assertHasDescendants(
                 'META-INF/MANIFEST.MF',
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaQuickstartIntegrationTest.groovy
index 7d0dd78..0f9e8f6 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaQuickstartIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaQuickstartIntegrationTest.groovy
@@ -16,19 +16,18 @@
 
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+
 import static org.hamcrest.Matchers.containsString
 
-class SamplesScalaQuickstartIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class SamplesScalaQuickstartIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final Sample sample = new Sample('scala/quickstart')
 
     private TestFile projectDir
@@ -44,11 +43,11 @@ class SamplesScalaQuickstartIntegrationTest {
         executer.inDirectory(projectDir).withTasks('clean', 'build').run()
 
         // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(projectDir)
         result.assertTestClassesExecuted('org.gradle.sample.impl.PersonImplTest')
 
         // Check contents of Jar
-        TestFile jarContents = dist.testDir.file('jar')
+        TestFile jarContents = file('jar')
         projectDir.file("build/libs/quickstart.jar").unzipTo(jarContents)
         jarContents.assertHasDescendants(
                 'META-INF/MANIFEST.MF',
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaZincIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaZincIntegrationTest.groovy
index ce628c9..adbedba 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaZincIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaZincIntegrationTest.groovy
@@ -17,18 +17,15 @@
 package org.gradle.integtests.samples
 
 import org.gradle.api.JavaVersion
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import spock.lang.IgnoreIf
-import spock.lang.Specification
 
 @IgnoreIf({!JavaVersion.current().java6Compatible})
-class SamplesScalaZincIntegrationTest extends Specification {
-    @Rule GradleDistribution dist = new GradleDistribution()
-    @Rule GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class SamplesScalaZincIntegrationTest extends AbstractIntegrationSpec {
+
     @Rule Sample sample = new Sample('scala/zinc')
 
     def canBuildJar() {
@@ -41,7 +38,7 @@ class SamplesScalaZincIntegrationTest extends Specification {
 
         then:
         // Check contents of Jar
-        TestFile jarContents = dist.testDir.file('jar')
+        TestFile jarContents = file('jar')
         projectDir.file("build/libs/zinc.jar").unzipTo(jarContents)
         jarContents.assertHasDescendants(
                 'META-INF/MANIFEST.MF',
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebProjectIntegrationTest.groovy
index 76f549e..b8d2338 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebProjectIntegrationTest.groovy
@@ -16,9 +16,9 @@
 
 package org.gradle.integtests.samples
 
-import org.gradle.integtests.fixtures.Sample
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.util.TestFile
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 
 /**
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebQuickstartIntegrationTest.groovy
index c8ec5ca..2891460 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebQuickstartIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebQuickstartIntegrationTest.groovy
@@ -18,7 +18,7 @@ package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
 import spock.lang.Timeout
 import spock.lang.Unroll
@@ -110,9 +110,10 @@ task sayHearthyGoodbye << {
             try {
                 url.text
                 return
-            } catch (ConnectException e) {
-                Thread.sleep(200)
+            } catch (IOException e) {
+                // continue
             }
+            Thread.sleep(200)
         }
         throw new RuntimeException("Timeout waiting for jetty to become available.")
     }
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/build.gradle
index d272675..ae579c0 100644
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/build.gradle
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/build.gradle
@@ -5,7 +5,7 @@ repositories {
 }
 
 dependencies {
-    testCompile 'junit:junit:4.8.2'
+    testCompile 'junit:junit:4.11'
     testCompile 'org.testng:testng:6.3.1'
 }
 
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/shared/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/shared/build.gradle
index 6058be2..a3ad914 100644
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/shared/build.gradle
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/shared/build.gradle
@@ -5,5 +5,5 @@ repositories {
 }
 
 dependencies {
-    testCompile 'junit:junit:4.8.2'
+    testCompile 'junit:junit:4.11'
 }
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ProjectLayoutIntegrationTest/canUseANonStandardBuildDir/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ProjectLayoutIntegrationTest/canUseANonStandardBuildDir/build.gradle
index d227c19..1b25483 100644
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ProjectLayoutIntegrationTest/canUseANonStandardBuildDir/build.gradle
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ProjectLayoutIntegrationTest/canUseANonStandardBuildDir/build.gradle
@@ -5,7 +5,7 @@ repositories {
 }
 
 dependencies {
-    testCompile 'junit:junit:4.8.2'
+    testCompile 'junit:junit:4.11'
 }
 
 buildDir = 'target'
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectWithConfigurationHierarchy.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectWithConfigurationHierarchy.gradle
deleted file mode 100644
index 3ab8766..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectWithConfigurationHierarchy.gradle
+++ /dev/null
@@ -1,58 +0,0 @@
-configurations {
-    compile
-    runtime { extendsFrom compile }
-}
-dependencies {
-    repositories {
-        ivy {
-            artifactPattern projectDir.absolutePath + '/[artifact]-[revision].jar'
-            ivyPattern projectDir.absolutePath + '/[module]-[revision]-ivy.xml'
-            ivyPattern projectDir.absolutePath + '/[module]-[revision]-ivy.xml'
-        }
-    }
-    compile group: 'test', name: 'projectA', version: '1.2', configuration: 'api'
-    runtime group: 'test', name: 'projectA', version: '1.2'
-    runtime group: 'test', name: 'projectB', version: '1.5', configuration: 'extraRuntime'
-}
-
-file("projectA-1.2.jar").text = ''
-file("projectB-1.5.jar").text = ''
-file("projectB-api-1.5.jar").text = ''
-file("projectB-extraRuntime-1.5.jar").text = ''
-
-defaultTasks 'listJars'
-
-task listJars << {
-    def compile = configurations.compile
-
-    Set jars = compile.collect { it.name } as Set
-    assert ['projectA-1.2.jar', 'projectB-api-1.5.jar'] as Set == jars
-
-    def projectA = compile.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectA' && it.configuration == 'api' }
-    def root = (projectA.parents as List)[0]
-    def artifacts = projectA.getAllArtifacts(root).collect { it.name } as Set
-    assert ['projectA', 'projectB-api'] as Set == artifacts
-
-    def projectB = projectA.children.find { it.moduleName == 'projectB' && it.configuration == 'compileTime' }
-    artifacts = projectB.getAllArtifacts(projectA).collect { it.name } as Set
-    assert ['projectB-api'] as Set == artifacts
-
-    def runtime = configurations.runtime
-
-    jars = runtime.collect { it.name } as Set
-    assert ['projectA-1.2.jar', 'projectB-api-1.5.jar', 'projectB-1.5.jar', 'projectB-extraRuntime-1.5.jar'] as Set == jars
-
-    projectA = runtime.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectA' && it.configuration == 'api' }
-    root = (projectA.parents as List)[0]
-    artifacts = projectA.getAllArtifacts(root).collect { it.name } as Set
-    assert ['projectA', 'projectB-api'] as Set == artifacts
-
-    projectA = runtime.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectA' && it.configuration == 'default' }
-    root = (projectA.parents as List)[0]
-    artifacts = projectA.getAllArtifacts(root).collect { it.name } as Set
-    assert ['projectA', 'projectB'] as Set == artifacts
-
-    projectB = runtime.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectB' && it.configuration == 'extraRuntime' }
-    artifacts = projectB.getAllArtifacts(root).collect { it.name } as Set
-    assert ['projectB', 'projectB-extraRuntime'] as Set == artifacts
-}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractAutoTestedSamplesTest.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractAutoTestedSamplesTest.groovy
index 966ff2e..9d536a8 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractAutoTestedSamplesTest.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractAutoTestedSamplesTest.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2011 the original author or authors.
+ * Copyright 2012 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractCompatibilityTestRunner.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractCompatibilityTestRunner.java
index a623e8b..bbee5a9 100755
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractCompatibilityTestRunner.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractCompatibilityTestRunner.java
@@ -15,18 +15,34 @@
  */
 package org.gradle.integtests.fixtures;
 
+import org.gradle.api.Action;
+import org.gradle.api.Transformer;
+import org.gradle.integtests.fixtures.executer.GradleDistribution;
+import org.gradle.integtests.fixtures.executer.UnderDevelopmentGradleDistribution;
+import org.gradle.integtests.fixtures.versions.ReleasedVersionDistributions;
 import org.gradle.internal.jvm.Jvm;
 import org.gradle.internal.os.OperatingSystem;
+import org.gradle.test.fixtures.file.TestDirectoryProvider;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
+import org.gradle.util.CollectionUtils;
+import org.gradle.util.GradleVersion;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
+import static org.gradle.util.CollectionUtils.*;
+
 /**
  * A base class for those test runners which execute a test multiple times against a set of Gradle versions.
  */
 public abstract class AbstractCompatibilityTestRunner extends AbstractMultiTestRunner {
-    protected final GradleDistribution current = new GradleDistribution();
-    protected final List<BasicGradleDistribution> previous;
+
+    private static final String VERSIONS_SYSPROP_NAME = "org.gradle.integtest.versions";
+    private final TestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider();
+    protected final GradleDistribution current = new UnderDevelopmentGradleDistribution();
+    protected final List<GradleDistribution> previous;
 
     protected AbstractCompatibilityTestRunner(Class<?> target) {
         this(target, null);
@@ -36,16 +52,16 @@ public abstract class AbstractCompatibilityTestRunner extends AbstractMultiTestR
         super(target);
         validateTestName(target);
 
-        previous = new ArrayList<BasicGradleDistribution>();
+        previous = new ArrayList<GradleDistribution>();
         if (versionStr == null) {
-            versionStr = System.getProperty("org.gradle.integtest.versions", "latest");
+            versionStr = System.getProperty(VERSIONS_SYSPROP_NAME, "latest");
         }
-        ReleasedVersions previousVersions = new ReleasedVersions(current);
-        if (!versionStr.equals("all")) {
-            previous.add(previousVersions.getLast());
-        } else {
-            List<BasicGradleDistribution> all = previousVersions.getAll();
-            for (BasicGradleDistribution previous : all) {
+        final ReleasedVersionDistributions previousVersions = new ReleasedVersionDistributions();
+        if (versionStr.equals("latest")) {
+            previous.add(previousVersions.getMostRecentFinalRelease());
+        } else if (versionStr.equals("all")) {
+            List<GradleDistribution> all = previousVersions.getAll();
+            for (GradleDistribution previous : all) {
                 if (!previous.worksWith(Jvm.current())) {
                     add(new IgnoredVersion(previous, "does not work with current JVM"));
                     continue;
@@ -56,6 +72,29 @@ public abstract class AbstractCompatibilityTestRunner extends AbstractMultiTestR
                 }
                 this.previous.add(previous);
             }
+        } else if (versionStr.matches("^\\d.*$")) {
+            String[] versions = versionStr.split(",");
+            List<GradleVersion> gradleVersions = CollectionUtils.sort(collect(Arrays.asList(versions), new Transformer<GradleVersion, String>() {
+                public GradleVersion transform(String versionString) {
+                    GradleVersion gradleVersion = GradleVersion.version(versionString);
+                    if (!gradleVersion.isValid()) {
+                        throw new RuntimeException("Specified Gradle version '" + versionString + "' is not a valid Gradle version");
+                    }
+                    return gradleVersion;
+                }
+            }), Collections.reverseOrder());
+
+            inject(previous, gradleVersions, new Action<InjectionStep<List<GradleDistribution>, GradleVersion>>() {
+                public void execute(InjectionStep<List<GradleDistribution>, GradleVersion> step) {
+                    GradleDistribution distribution = previousVersions.getDistribution(step.getItem());
+                    if (distribution == null) {
+                        throw new RuntimeException("Gradle version '" + step.getItem().getVersion() + "' is not a valid testable released version");
+                    }
+                    step.getTarget().add(distribution);
+                }
+            });
+        } else {
+            throw new RuntimeException("Invalid value for " + VERSIONS_SYSPROP_NAME + " system property: " + versionStr + "(valid values: 'all', 'latest' or comma separated list of versions)");
         }
     }
 
@@ -73,15 +112,15 @@ public abstract class AbstractCompatibilityTestRunner extends AbstractMultiTestR
         }
     }
 
-    public List<BasicGradleDistribution> getPrevious() {
+    public List<GradleDistribution> getPrevious() {
         return previous;
     }
 
     private static class IgnoredVersion extends Execution {
-        private final BasicGradleDistribution distribution;
+        private final GradleDistribution distribution;
         private final String why;
 
-        private IgnoredVersion(BasicGradleDistribution distribution, String why) {
+        private IgnoredVersion(GradleDistribution distribution, String why) {
             this.distribution = distribution;
             this.why = why;
         }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractDelegatingGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractDelegatingGradleExecuter.java
deleted file mode 100644
index 24de52a..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractDelegatingGradleExecuter.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures;
-
-import org.gradle.launcher.daemon.registry.DaemonRegistry;
-
-public abstract class AbstractDelegatingGradleExecuter extends AbstractGradleExecuter {
-    @Override
-    protected ExecutionResult doRun() {
-        return checkResult(configureExecuter().run());
-    }
-
-    @Override
-    protected ExecutionFailure doRunWithFailure() {
-        return checkResult(configureExecuter().runWithFailure());
-    }
-
-    public DaemonRegistry getDaemonRegistry() {
-        return configureExecuter().getDaemonRegistry();
-    }
-
-    public void assertCanExecute() throws AssertionError {
-        configureExecuter().assertCanExecute();
-    }
-
-    @Override
-    public GradleHandle doStart() {
-        return configureExecuter().start();
-    }
-
-    protected abstract GradleExecuter configureExecuter();
-
-    protected <T extends ExecutionResult> T checkResult(T result) {
-        return result;
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractDependencyResolutionTest.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractDependencyResolutionTest.groovy
new file mode 100644
index 0000000..e9e5fcd
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractDependencyResolutionTest.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures
+
+import org.gradle.test.fixtures.ivy.IvyFileRepository
+import org.gradle.test.fixtures.ivy.IvyHttpRepository
+import org.gradle.test.fixtures.maven.MavenFileRepository
+import org.gradle.test.fixtures.maven.MavenHttpRepository
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.gradle.util.GradleVersion
+import org.junit.Rule
+
+import static org.gradle.test.matchers.UserAgentMatcher.matchesNameAndVersion
+
+abstract class AbstractDependencyResolutionTest extends AbstractIntegrationSpec {
+    @Rule public final HttpServer server = new HttpServer()
+
+    def "setup"() {
+        server.expectUserAgent(matchesNameAndVersion("Gradle", GradleVersion.current().getVersion()))
+        requireOwnGradleUserHomeDir()
+    }
+
+    IvyFileRepository ivyRepo(def dir = 'ivy-repo') {
+        return ivy(dir)
+    }
+
+    IvyHttpRepository getIvyHttpRepo() {
+        return new IvyHttpRepository(server, "/repo", ivyRepo)
+    }
+
+    IvyHttpRepository ivyHttpRepo(String name) {
+        assert !name.startsWith("/")
+        return new IvyHttpRepository(server, "/${name}", ivyRepo(name))
+    }
+
+    MavenFileRepository mavenRepo(String name = "repo") {
+        return maven(name)
+    }
+
+    MavenHttpRepository getMavenHttpRepo() {
+        return new MavenHttpRepository(server, "/repo", mavenRepo)
+    }
+
+    MavenHttpRepository mavenHttpRepo(String name) {
+        assert !name.startsWith("/")
+        return new MavenHttpRepository(server, "/${name}", mavenRepo(name))
+    }
+
+    MavenHttpRepository mavenHttpRepo(String contextPath, MavenFileRepository backingRepo) {
+        return new MavenHttpRepository(server, contextPath, backingRepo)
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractGradleExecuter.java
deleted file mode 100644
index 1acb363..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractGradleExecuter.java
+++ /dev/null
@@ -1,430 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures;
-
-import groovy.lang.Closure;
-import org.gradle.api.Action;
-import org.gradle.api.internal.ClosureBackedAction;
-import org.gradle.internal.jvm.Jvm;
-import org.gradle.listener.ActionBroadcast;
-import org.gradle.util.TextUtil;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.InputStream;
-import java.nio.charset.Charset;
-import java.util.*;
-
-import static java.util.Arrays.asList;
-
-public abstract class AbstractGradleExecuter implements GradleExecuter {
-
-    // If no explicit timeout is specified, this will be used.
-    // This is to avoid daemons “accidentally” launched hanging around for a long time.
-    private static final int DEFAULT_DAEMON_IDLE_TIMEOUT_SECS = 10;
-
-    // Specified in the build config to point under /build
-    // This is primarily to avoid filling ~/.gradle on CI builds
-    private static final String DEFAULT_DAEMON_REGISTRY_DIR_PROPERTY = "org.gradle.integtest.daemon.registry";
-
-    private final List<String> args = new ArrayList<String>();
-    private final List<String> tasks = new ArrayList<String>();
-    private File workingDir;
-    private boolean quiet;
-    private boolean taskList;
-    private boolean dependencyList;
-    private boolean searchUpwards;
-    private Map<String, String> environmentVars = new HashMap<String, String>();
-    private List<File> initScripts = new ArrayList<File>();
-    private String executable;
-    private File gradleUserHomeDir;
-    private File userHomeDir;
-    private File javaHome;
-    private File buildScript;
-    private File projectDir;
-    private File settingsFile;
-    private InputStream stdin;
-    private String defaultCharacterEncoding;
-    private Integer daemonIdleTimeoutSecs;
-    private File daemonBaseDir;
-    //gradle opts make sense only for forking executer but having them here makes more sense
-    protected final List<String> gradleOpts = new ArrayList<String>();
-    protected boolean noDefaultJvmArgs;
-    private final ActionBroadcast<GradleExecuter> beforeExecute = new ActionBroadcast<GradleExecuter>();
-
-    public GradleExecuter reset() {
-        args.clear();
-        tasks.clear();
-        initScripts.clear();
-        workingDir = null;
-        projectDir = null;
-        buildScript = null;
-        settingsFile = null;
-        quiet = false;
-        taskList = false;
-        dependencyList = false;
-        searchUpwards = false;
-        executable = null;
-        gradleUserHomeDir = null;
-        javaHome = null;
-        environmentVars.clear();
-        stdin = null;
-        defaultCharacterEncoding = null;
-        daemonIdleTimeoutSecs = null;
-        daemonBaseDir = null;
-        noDefaultJvmArgs = false;
-        return this;
-    }
-
-    public void beforeExecute(Action<? super GradleExecuter> action) {
-        beforeExecute.add(action);
-    }
-
-    public void beforeExecute(Closure action) {
-        beforeExecute.add(new ClosureBackedAction<GradleExecuter>(action));
-    }
-
-    public GradleExecuter inDirectory(File directory) {
-        workingDir = directory;
-        return this;
-    }
-
-    public File getWorkingDir() {
-        return workingDir;
-    }
-
-    protected void copyTo(GradleExecuter executer) {
-        if (workingDir != null) {
-            executer.inDirectory(workingDir);
-        }
-        if (projectDir != null) {
-            executer.usingProjectDirectory(projectDir);
-        }
-        if (buildScript != null) {
-            executer.usingBuildScript(buildScript);
-        }
-        if (settingsFile != null) {
-            executer.usingSettingsFile(settingsFile);
-        }
-        if (javaHome != null) {
-            executer.withJavaHome(javaHome);
-        }
-        for (File initScript : initScripts) {
-            executer.usingInitScript(initScript);
-        }
-        executer.withTasks(tasks);
-        executer.withArguments(args);
-        executer.withEnvironmentVars(getAllEnvironmentVars());
-        executer.usingExecutable(executable);
-        if (quiet) {
-            executer.withQuietLogging();
-        }
-        if (taskList) {
-            executer.withTaskList();
-        }
-        if (dependencyList) {
-            executer.withDependencyList();
-        }
-        executer.withGradleUserHomeDir(gradleUserHomeDir);
-        if (userHomeDir != null) {
-            executer.withUserHomeDir(userHomeDir);
-        }
-        if (stdin != null) {
-            executer.withStdIn(stdin);
-        }
-        if (defaultCharacterEncoding != null) {
-            executer.withDefaultCharacterEncoding(defaultCharacterEncoding);
-        }
-        executer.withGradleOpts(gradleOpts.toArray(new String[gradleOpts.size()]));
-        if (daemonIdleTimeoutSecs != null) {
-            executer.withDaemonIdleTimeoutSecs(daemonIdleTimeoutSecs);
-        }
-        if (daemonBaseDir != null) {
-            executer.withDaemonBaseDir(daemonBaseDir);
-        }
-        if (noDefaultJvmArgs) {
-            executer.withNoDefaultJvmArgs();
-        }
-    }
-
-    public GradleExecuter usingBuildScript(File buildScript) {
-        this.buildScript = buildScript;
-        return this;
-    }
-
-    public GradleExecuter usingProjectDirectory(File projectDir) {
-        this.projectDir = projectDir;
-        return this;
-    }
-
-    public GradleExecuter usingSettingsFile(File settingsFile) {
-        this.settingsFile = settingsFile;
-        return this;
-    }
-
-    public GradleExecuter usingInitScript(File initScript) {
-        initScripts.add(initScript);
-        return this;
-    }
-
-    public GradleExecuter withGradleUserHomeDir(File userHomeDir) {
-        this.gradleUserHomeDir = userHomeDir;
-        return this;
-    }
-
-    public File getUserHomeDir() {
-        return userHomeDir;
-    }
-
-    public GradleExecuter withUserHomeDir(File userHomeDir) {
-        this.userHomeDir = userHomeDir;
-        return this;
-    }
-
-    public File getJavaHome() {
-        return javaHome == null ? Jvm.current().getJavaHome() : javaHome;
-    }
-
-    public GradleExecuter withJavaHome(File javaHome) {
-        this.javaHome = javaHome;
-        return this;
-    }
-
-    public GradleExecuter usingExecutable(String script) {
-        this.executable = script;
-        return this;
-    }
-
-    public String getExecutable() {
-        return executable;
-    }
-
-    public GradleExecuter withStdIn(String text) {
-        this.stdin = new ByteArrayInputStream(TextUtil.toPlatformLineSeparators(text).getBytes());
-        return this;
-    }
-
-    public GradleExecuter withStdIn(InputStream stdin) {
-        this.stdin = stdin;
-        return this;
-    }
-
-    public InputStream getStdin() {
-        return stdin == null ? new ByteArrayInputStream(new byte[0]) : stdin;
-    }
-
-    public GradleExecuter withDefaultCharacterEncoding(String defaultCharacterEncoding) {
-        this.defaultCharacterEncoding = defaultCharacterEncoding;
-        return this;
-    }
-
-    public String getDefaultCharacterEncoding() {
-        return defaultCharacterEncoding == null ? Charset.defaultCharset().name() : defaultCharacterEncoding;
-    }
-
-    public GradleExecuter withSearchUpwards() {
-        searchUpwards = true;
-        return this;
-    }
-
-    public boolean isQuiet() {
-        return quiet;
-    }
-
-    public GradleExecuter withQuietLogging() {
-        quiet = true;
-        return this;
-    }
-
-    public GradleExecuter withTaskList() {
-        taskList = true;
-        return this;
-    }
-
-    public GradleExecuter withDependencyList() {
-        dependencyList = true;
-        return this;
-    }
-
-    public GradleExecuter withArguments(String... args) {
-        return withArguments(Arrays.asList(args));
-    }
-
-    public GradleExecuter withArguments(List<String> args) {
-        this.args.clear();
-        this.args.addAll(args);
-        return this;
-    }
-
-    public GradleExecuter withArgument(String arg) {
-        this.args.add(arg);
-        return this;
-    }
-
-    public GradleExecuter withEnvironmentVars(Map<String, ?> environment) {
-        environmentVars.clear();
-        for (Map.Entry<String, ?> entry : environment.entrySet()) {
-            environmentVars.put(entry.getKey(), entry.getValue().toString());
-        }
-        return this;
-    }
-
-    protected Map<String, String> getAllEnvironmentVars() {
-        return environmentVars;
-    }
-
-    public Map<String, String> getEnvironmentVars() {
-        return environmentVars;
-    }
-
-    public GradleExecuter withTasks(String... names) {
-        return withTasks(Arrays.asList(names));
-    }
-
-    public GradleExecuter withTasks(List<String> names) {
-        tasks.clear();
-        tasks.addAll(names);
-        return this;
-    }
-
-    public GradleExecuter withDaemonIdleTimeoutSecs(int secs) {
-        daemonIdleTimeoutSecs = secs;
-        return this;
-    }
-
-    public GradleExecuter withNoDefaultJvmArgs() {
-        noDefaultJvmArgs = true;
-        return this;
-    }
-
-    protected Integer getDaemonIdleTimeoutSecs() {
-        return daemonIdleTimeoutSecs;
-    }
-
-    public GradleExecuter withDaemonBaseDir(File daemonBaseDir) {
-        this.daemonBaseDir = daemonBaseDir;
-        return this;
-    }
-
-    protected File getDaemonBaseDir() {
-        return daemonBaseDir;
-    }
-
-    protected List<String> getAllArgs() {
-        List<String> allArgs = new ArrayList<String>();
-        if (buildScript != null) {
-            allArgs.add("--build-file");
-            allArgs.add(buildScript.getAbsolutePath());
-        }
-        if (projectDir != null) {
-            allArgs.add("--project-dir");
-            allArgs.add(projectDir.getAbsolutePath());
-        }
-        for (File initScript : initScripts) {
-            allArgs.add("--init-script");
-            allArgs.add(initScript.getAbsolutePath());
-        }
-        if (settingsFile != null) {
-            allArgs.add("--settings-file");
-            allArgs.add(settingsFile.getAbsolutePath());
-        }
-        if (quiet) {
-            allArgs.add("--quiet");
-        }
-        if (taskList) {
-            allArgs.add("tasks");
-        }
-        if (dependencyList) {
-            allArgs.add("dependencies");
-        }
-        if (!searchUpwards) {
-            allArgs.add("--no-search-upward");
-        }
-        if (gradleUserHomeDir != null) {
-            allArgs.add("--gradle-user-home");
-            allArgs.add(gradleUserHomeDir.getAbsolutePath());
-        }
-
-        // Prevent from running with the default idle timeout as it causes CI chaos
-        int effectiveDaemonIdleTimeoutSecs = daemonIdleTimeoutSecs == null ? DEFAULT_DAEMON_IDLE_TIMEOUT_SECS : daemonIdleTimeoutSecs;
-        allArgs.add("-Dorg.gradle.daemon.idletimeout=" + effectiveDaemonIdleTimeoutSecs * 1000);
-
-        // Prevent from running with the default daemon dir (~/.gradle/daemon) as it fills up on the CI server
-        String effectiveDaemonBaseDir = null;
-        if (daemonBaseDir == null) {
-            effectiveDaemonBaseDir = System.getProperty(DEFAULT_DAEMON_REGISTRY_DIR_PROPERTY);
-        } else {
-            effectiveDaemonBaseDir = daemonBaseDir.getAbsolutePath();
-        }
-        if (effectiveDaemonBaseDir != null) {
-            args.add("-Dorg.gradle.daemon.registry.base=" + effectiveDaemonBaseDir);
-        }
-
-        allArgs.addAll(args);
-        allArgs.addAll(tasks);
-        return allArgs;
-    }
-
-    public final GradleHandle start() {
-        fireBeforeExecute();
-        assertCanExecute();
-        try {
-            return doStart();
-        } finally {
-            reset();
-        }
-    }
-
-    public final ExecutionResult run() {
-        fireBeforeExecute();
-        assertCanExecute();
-        try {
-            return doRun();
-        } finally {
-            reset();
-        }
-    }
-
-    public final ExecutionFailure runWithFailure() {
-        fireBeforeExecute();
-        assertCanExecute();
-        try {
-            return doRunWithFailure();
-        } finally {
-            reset();
-        }
-    }
-
-    private void fireBeforeExecute() {
-        beforeExecute.execute(this);
-    }
-
-    protected GradleHandle doStart() {
-        throw new UnsupportedOperationException(String.format("%s does not support running asynchronously.", getClass().getSimpleName()));
-    }
-
-    protected abstract ExecutionResult doRun();
-
-    protected abstract ExecutionFailure doRunWithFailure();
-
-    /**
-     * {@inheritDoc}
-     */
-    public AbstractGradleExecuter withGradleOpts(String ... gradleOpts) {
-        this.gradleOpts.addAll(asList(gradleOpts));
-        return this;
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractIntegrationSpec.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractIntegrationSpec.groovy
index e14f61d..46963ea 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractIntegrationSpec.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractIntegrationSpec.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2011 the original author or authors.
+ * Copyright 2012 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,8 +15,13 @@
  */
 package org.gradle.integtests.fixtures
 
+import org.gradle.api.Action
+import org.gradle.integtests.fixtures.executer.*
+import org.gradle.test.fixtures.file.TestDirectoryProvider
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.test.fixtures.ivy.IvyFileRepository
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.maven.MavenFileRepository
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -25,30 +30,34 @@ import spock.lang.Specification
  * 
  * Plan is to bring features over as needed.
  */
-class AbstractIntegrationSpec extends Specification {
-    
-    @Rule final GradleDistribution distribution = new GradleDistribution()
-    @Rule final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class AbstractIntegrationSpec extends Specification implements TestDirectoryProvider {
+
+    @Rule final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
+
+    GradleDistribution distribution = new UnderDevelopmentGradleDistribution()
+    GradleExecuter executer = new GradleContextualExecuter(distribution, temporaryFolder)
 
     ExecutionResult result
     ExecutionFailure failure
     private MavenFileRepository mavenRepo
     private IvyFileRepository ivyRepo
 
+    private List<Closure> executerActions = []
+
     protected TestFile getBuildFile() {
-        testDir.file('build.gradle')
+        testDirectory.file('build.gradle')
     }
 
     protected TestFile getSettingsFile() {
-        testDir.file('settings.gradle')
+        testDirectory.file('settings.gradle')
     }
 
-    protected TestFile getTestDir() {
-        distribution.getTestDir();
+    TestFile getTestDirectory() {
+        temporaryFolder.testDirectory
     }
 
     protected TestFile file(Object... path) {
-        getTestDir().file(path);
+        getTestDirectory().file(path);
     }
 
     protected GradleExecuter sample(Sample sample) {
@@ -67,9 +76,9 @@ class AbstractIntegrationSpec extends Specification {
         executer.usingProjectDirectory(file(path))
     }
 
-    protected GradleDistribution requireOwnUserHomeDir() {
-        distribution.requireOwnUserHomeDir()
-        distribution
+    protected GradleExecuter requireOwnGradleUserHomeDir() {
+        executer.requireOwnGradleUserHomeDir()
+        executer
     }
 
     /**
@@ -88,14 +97,20 @@ class AbstractIntegrationSpec extends Specification {
     }
 
     protected ExecutionResult succeeds(String... tasks) {
+        supplyExecuterActions()
         result = executer.withTasks(*tasks).run()
     }
 
+    void supplyExecuterActions() {
+        executerActions.each { it(executer) }
+    }
+
     protected ExecutionFailure runAndFail(String... tasks) {
         fails(*tasks)
     }
     
     protected ExecutionFailure fails(String... tasks) {
+        supplyExecuterActions()
         failure = executer.withTasks(*tasks).runWithFailure()
         result = failure
     }
@@ -138,9 +153,9 @@ class AbstractIntegrationSpec extends Specification {
     }
 
     ArtifactBuilder artifactBuilder() {
-        def executer = distribution.executer()
-        executer.withGradleUserHomeDir(distribution.getUserHomeDir())
-        return new GradleBackedArtifactBuilder(executer, getTestDir().file("artifacts"))
+        def executer = distribution.executer(temporaryFolder)
+        executer.withGradleUserHomeDir(this.executer.getGradleUserHomeDir())
+        return new GradleBackedArtifactBuilder(executer, getTestDirectory().file("artifacts"))
     }
 
     public MavenFileRepository maven(TestFile repo) {
@@ -173,6 +188,16 @@ class AbstractIntegrationSpec extends Specification {
         return ivyRepo
     }
 
+    public GradleExecuter using(Action<GradleExecuter> action) {
+        action.execute(executer)
+        executer
+    }
+
+    public GradleExecuter alwaysUsing(Closure executerAction) {
+        executerActions.add(executerAction)
+        executer
+    }
+
     def createZip(String name, Closure cl) {
         TestFile zipRoot = file("${name}.root")
         TestFile zip = file(name)
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractIntegrationTest.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractIntegrationTest.java
index 742ddeb..0a9f1ce 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractIntegrationTest.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractIntegrationTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2011 the original author or authors.
+ * Copyright 2012 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,27 +15,65 @@
  */
 package org.gradle.integtests.fixtures;
 
+import com.google.common.collect.Sets;
+import org.gradle.integtests.fixtures.executer.*;
+import org.gradle.test.fixtures.file.TestDirectoryProvider;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.gradle.test.fixtures.ivy.IvyFileRepository;
-import org.gradle.test.fixtures.ivy.IvyRepository;
-import org.gradle.util.TestFile;
-import org.gradle.util.TestFileContext;
+import org.gradle.test.fixtures.maven.MavenFileRepository;
+import org.junit.Before;
+import org.junit.ClassRule;
 import org.junit.Rule;
 
 import java.io.File;
+import java.util.Set;
 
-public abstract class AbstractIntegrationTest implements TestFileContext {
-    @Rule public final GradleDistribution distribution = new GradleDistribution();
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter();
+public abstract class AbstractIntegrationTest implements TestDirectoryProvider {
+    @Rule public final TestNameTestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider();
+    public final GradleDistribution distribution = new UnderDevelopmentGradleDistribution();
+    public final GradleExecuter executer = new GradleContextualExecuter(distribution, this);
+
+    @ClassRule public static final TestNameTestDirectoryProvider SHARED_TEST_DIRECTORY_PROVIDER = new TestNameTestDirectoryProvider();
+    public static final GradleDistribution SHARED_DISTRIBUTION = new UnderDevelopmentGradleDistribution();
+    private static final GradleExecuter SHARED_EXECUTER = new GradleContextualExecuter(SHARED_DISTRIBUTION, SHARED_TEST_DIRECTORY_PROVIDER);
+
+    private static final Set<Class<?>> SHARED_BUILD_RUN_CLASSES = Sets.newHashSet();
+
+    protected boolean useSharedBuild;
 
     private MavenFileRepository mavenRepo;
     private IvyFileRepository ivyRepo;
 
-    public TestFile getTestDir() {
-        return distribution.getTestDir();
+    protected GradleDistribution getDistribution() {
+        return useSharedBuild ? SHARED_DISTRIBUTION : distribution;
+    }
+
+    protected GradleExecuter getExecuter() {
+        return useSharedBuild ? SHARED_EXECUTER : executer;
+    }
+
+    protected TestNameTestDirectoryProvider getTestDirectoryProvider() {
+        return useSharedBuild ? SHARED_TEST_DIRECTORY_PROVIDER : testDirectoryProvider;
+    }
+
+    protected void runSharedBuild() {}
+
+    @Before
+    public void doRunSharedBuild() {
+        if (SHARED_BUILD_RUN_CLASSES.add(getClass())) {
+            useSharedBuild = true;
+            runSharedBuild();
+            useSharedBuild = false;
+        }
+    }
+
+    public TestFile getTestDirectory() {
+        return getTestDirectoryProvider().getTestDirectory();
     }
 
     public TestFile file(Object... path) {
-        return getTestDir().file(path);
+        return getTestDirectory().file(path);
     }
 
     public TestFile testFile(String name) {
@@ -43,51 +81,51 @@ public abstract class AbstractIntegrationTest implements TestFileContext {
     }
 
     protected GradleExecuter inTestDirectory() {
-        return inDirectory(getTestDir());
+        return inDirectory(getTestDirectory());
     }
 
     protected GradleExecuter inDirectory(File directory) {
-        return executer.inDirectory(directory);
+        return getExecuter().inDirectory(directory);
     }
 
     protected GradleExecuter usingBuildFile(File file) {
-        return executer.usingBuildScript(file);
+        return getExecuter().usingBuildScript(file);
     }
 
     protected GradleExecuter usingProjectDir(File projectDir) {
-        return executer.usingProjectDirectory(projectDir);
+        return getExecuter().usingProjectDirectory(projectDir);
     }
 
     protected ArtifactBuilder artifactBuilder() {
-        GradleDistributionExecuter gradleExecuter = distribution.executer();
-        gradleExecuter.withGradleUserHomeDir(distribution.getUserHomeDir());
-        return new GradleBackedArtifactBuilder(gradleExecuter, getTestDir().file("artifacts"));
+        GradleExecuter gradleExecuter = getDistribution().executer(testDirectoryProvider);
+        gradleExecuter.withGradleUserHomeDir(getExecuter().getGradleUserHomeDir());
+        return new GradleBackedArtifactBuilder(gradleExecuter, getTestDirectory().file("artifacts"));
     }
 
-    public MavenRepository maven(TestFile repo) {
+    public MavenFileRepository maven(TestFile repo) {
         return new MavenFileRepository(repo);
     }
 
-    public MavenRepository maven(Object repo) {
+    public MavenFileRepository maven(Object repo) {
         return new MavenFileRepository(file(repo));
     }
 
-    public MavenRepository getMavenRepo() {
+    public MavenFileRepository getMavenRepo() {
         if (mavenRepo == null) {
             mavenRepo = new MavenFileRepository(file("maven-repo"));
         }
         return mavenRepo;
     }
 
-    public IvyRepository ivy(TestFile repo) {
+    public IvyFileRepository ivy(TestFile repo) {
         return new IvyFileRepository(repo);
     }
 
-    public IvyRepository ivy(Object repo) {
+    public IvyFileRepository ivy(Object repo) {
         return new IvyFileRepository(file(repo));
     }
 
-    public IvyRepository getIvyRepo() {
+    public IvyFileRepository getIvyRepo() {
         if (ivyRepo == null) {
             ivyRepo = new IvyFileRepository(file("ivy-repo"));
         }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ArtifactBuilder.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ArtifactBuilder.java
deleted file mode 100644
index 0c44b87..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ArtifactBuilder.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures;
-
-import org.gradle.util.TestFile;
-
-import java.io.File;
-
-public interface ArtifactBuilder {
-    TestFile sourceFile(String path);
-
-    TestFile resourceFile(String path);
-
-    void buildJar(File jarFile);
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/BasicGradleDistribution.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/BasicGradleDistribution.java
deleted file mode 100644
index b3331a4..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/BasicGradleDistribution.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures;
-
-import org.gradle.internal.os.OperatingSystem;
-import org.gradle.internal.jvm.Jvm;
-import org.gradle.util.TestFile;
-
-public interface BasicGradleDistribution {
-    /**
-     * Returns the root directory of the installed distribution
-     */
-    TestFile getGradleHomeDir();
-
-    /**
-     * Returns the binary distribution.
-     */
-    TestFile getBinDistribution();
-
-    /**
-     * Returns the version of this distribution.
-     */
-    String getVersion();
-
-    /**
-     * Creates an executer which will use this distribution.
-     */
-    GradleExecuter executer();
-
-    /**
-     * Returns true if this distribution supports the given JVM.
-     */
-    boolean worksWith(Jvm jvm);
-
-    /**
-     * Returns true if this distribution supports the given Operating system.
-     */
-    boolean worksWith(OperatingSystem os);
-
-    /**
-     * Returns true if the daemon is supported by this distribution.
-     */
-    boolean isDaemonSupported();
-
-    /**
-     * Returns true if the configuring daemon idle timeout feature is supported by this distribution.
-     */
-    boolean isDaemonIdleTimeoutConfigurable();
-
-    /**
-     *
-     * Returns true if the tooling API is supported by this distribution.
-     */
-    boolean isToolingApiSupported();
-
-    /**
-     * Returns true if the open API is supported by this distribution.
-     */
-    boolean isOpenApiSupported();
-
-    /**
-     * Returns true if the cache implementation in this distribution is multi-process safe.
-     */
-    boolean isMultiProcessSafeCache();
-
-    /**
-     * Returns true if the wrapper from this distribution can execute a build using the specified version.
-     */
-    boolean wrapperCanExecute(String version);
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CrossVersionIntegrationSpec.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CrossVersionIntegrationSpec.groovy
index 4e1efdc..b6cc760 100755
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CrossVersionIntegrationSpec.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CrossVersionIntegrationSpec.groovy
@@ -15,31 +15,42 @@
  */
 package org.gradle.integtests.fixtures
 
-import org.gradle.util.TestFile
+import org.gradle.integtests.fixtures.executer.GradleDistribution
+import org.gradle.integtests.fixtures.executer.UnderDevelopmentGradleDistribution
+import org.gradle.test.fixtures.file.TestDirectoryProvider
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.test.fixtures.maven.MavenFileRepository
+import org.gradle.test.fixtures.maven.MavenRepository
 import org.junit.Rule
 import org.junit.runner.RunWith
 import spock.lang.Specification
 
 @RunWith(CrossVersionTestRunner)
-abstract class CrossVersionIntegrationSpec extends Specification {
-    @Rule public final GradleDistribution current = new GradleDistribution()
-    static BasicGradleDistribution previous
+abstract class CrossVersionIntegrationSpec extends Specification implements TestDirectoryProvider {
+    @Rule TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
+    final GradleDistribution current = new UnderDevelopmentGradleDistribution()
+    static GradleDistribution previous
     private MavenFileRepository mavenRepo
 
-    BasicGradleDistribution getPrevious() {
+    GradleDistribution getPrevious() {
         return previous
     }
 
     protected TestFile getBuildFile() {
-        testDir.file('build.gradle')
+        testDirectory.file('build.gradle')
     }
 
-    protected TestFile getTestDir() {
-        current.getTestDir();
+    protected TestFile getSettingsFile() {
+        testDirectory.file('settings.gradle')
+    }
+
+    TestFile getTestDirectory() {
+        temporaryFolder.getTestDirectory();
     }
 
     protected TestFile file(Object... path) {
-        testDir.file(path);
+        testDirectory.file(path);
     }
 
     protected MavenRepository getMavenRepo() {
@@ -49,17 +60,10 @@ abstract class CrossVersionIntegrationSpec extends Specification {
         return mavenRepo
     }
 
-    def version(BasicGradleDistribution dist) {
-        def executer = dist.executer();
-        if (executer instanceof GradleDistributionExecuter) {
-            executer.withDeprecationChecksDisabled()
-        }
-        if (dist.multiProcessSafeCache) {
-            executer.withGradleUserHomeDir(current.userHomeDir)
-        } else {
-            executer.withGradleUserHomeDir(current.file("user-home/$dist.version"))
-        }
-        executer.inDirectory(testDir)
+    def version(GradleDistribution dist) {
+        def executer = dist.executer(temporaryFolder)
+        executer.withDeprecationChecksDisabled()
+        executer.inDirectory(testDirectory)
         return executer;
     }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CrossVersionTestRunner.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CrossVersionTestRunner.groovy
index 4e3d911..1839598 100755
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CrossVersionTestRunner.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CrossVersionTestRunner.groovy
@@ -15,6 +15,7 @@
  */
 package org.gradle.integtests.fixtures
 
+import org.gradle.integtests.fixtures.executer.GradleDistribution
 import org.gradle.util.GradleVersion
 
 /**
@@ -22,7 +23,8 @@ import org.gradle.util.GradleVersion
  *
  * <p>Sets the {@link CrossVersionIntegrationSpec#previous} property of the test instance before executing it.
  *
- * <p>A test class can be annotated with {@link TargetVersions} to specify the set of versions the test is compatible with.
+ * <p>A test class can be annotated with {@link TargetVersions} to specify the set of versions the test is compatible with, and {@link IgnoreVersions} to specify the set of versions the
+ * test should not be run for.
  */
 class CrossVersionTestRunner extends AbstractCompatibilityTestRunner {
     CrossVersionTestRunner(Class<? extends CrossVersionIntegrationSpec> target) {
@@ -31,19 +33,56 @@ class CrossVersionTestRunner extends AbstractCompatibilityTestRunner {
 
     @Override
     protected void createExecutions() {
-        previous.each { add(new PreviousVersionExecution(it)) }
+        for (GradleDistribution version : previous) {
+            add(new PreviousVersionExecution(version, isEnabled(version)))
+        }
+    }
+
+    protected boolean isEnabled(GradleDistribution previousVersion) {
+        Closure ignoreVersions = getAnnotationClosure(target, IgnoreVersions, {})
+        if (ignoreVersions(previousVersion)) {
+            return false
+        }
+
+        def versionsAnnotation = target.getAnnotation(TargetVersions)
+        if (versionsAnnotation == null) {
+            return true
+        }
+
+        List<String> targetGradleVersions = versionsAnnotation.value()
+        for (String targetGradleVersion : targetGradleVersions) {
+            if (isMatching(targetGradleVersion, previousVersion.version.version)) {
+                return true
+            }
+        }
+        return false
+    }
+
+    private static boolean isMatching(String targetGradleVersion, String candidate) {
+        if (targetGradleVersion.endsWith('+')) {
+            def minVersion = targetGradleVersion.substring(0, targetGradleVersion.length() - 1)
+            return GradleVersion.version(minVersion) <= GradleVersion.version(candidate)
+        }
+        return targetGradleVersion == candidate
+    }
+
+    private static Closure getAnnotationClosure(Class target, Class annotation, Closure defaultValue) {
+        def a = target.getAnnotation(annotation)
+        a ? a.value().newInstance(target, target) : defaultValue
     }
 
     private static class PreviousVersionExecution extends AbstractMultiTestRunner.Execution {
-        final BasicGradleDistribution previousVersion
+        final GradleDistribution previousVersion
+        final boolean enabled
 
-        PreviousVersionExecution(BasicGradleDistribution previousVersion) {
+        PreviousVersionExecution(GradleDistribution previousVersion, boolean enabled) {
             this.previousVersion = previousVersion
+            this.enabled = enabled
         }
 
         @Override
         String getDisplayName() {
-            return previousVersion.version
+            return previousVersion.version.version
         }
 
         @Override
@@ -53,24 +92,7 @@ class CrossVersionTestRunner extends AbstractCompatibilityTestRunner {
 
         @Override
         protected boolean isEnabled() {
-            TargetVersions targetGradleVersions = target.getAnnotation(TargetVersions)
-            if (!targetGradleVersions) {
-                return true
-            }
-            for (String targetGradleVersion: targetGradleVersions.value()) {
-                if (isMatching(targetGradleVersion, previousVersion.version)) {
-                    return true
-                }
-            }
-            return false
-        }
-        
-        private boolean isMatching(String targetGradleVersion, String candidate) {
-            if (targetGradleVersion.endsWith('+')) {
-                def minVersion = targetGradleVersion.substring(0, targetGradleVersion.length() - 1)
-                return GradleVersion.version(minVersion) <= GradleVersion.version(candidate)
-            }
-            return targetGradleVersion == candidate
+            return enabled
         }
     }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/DaemonGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/DaemonGradleExecuter.java
deleted file mode 100644
index 024482f..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/DaemonGradleExecuter.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures;
-
-import org.apache.commons.collections.CollectionUtils;
-import org.gradle.api.JavaVersion;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static java.util.Arrays.asList;
-
-class DaemonGradleExecuter extends ForkingGradleExecuter {
-    private final boolean allowExtraLogging;
-    private final boolean noDefaultJvmArgs;
-
-    public DaemonGradleExecuter(GradleDistribution distribution, boolean allowExtraLogging, boolean noDefaultJvmArgs) {
-        super(distribution.getGradleHomeDir());
-        this.allowExtraLogging = allowExtraLogging;
-        this.noDefaultJvmArgs = noDefaultJvmArgs;
-    }
-
-    @Override
-    protected List<String> getAllArgs() {
-        List<String> originalArgs = super.getAllArgs();
-
-        List<String> args = new ArrayList<String>();
-        args.add("--daemon");
-
-        args.addAll(originalArgs);
-        configureJvmArgs(args);
-        configureDefaultLogging(args);
-
-        if (getUserHomeDir() != null) {
-            args.add(String.format("-Duser.home=%s", getUserHomeDir().getPath()));
-        }
-
-        return args;
-    }
-
-    private void configureDefaultLogging(List<String> args) {
-        if(!allowExtraLogging) {
-            return;
-        }
-        List logOptions = asList("-i", "--info", "-d", "--debug", "-q", "--quiet");
-        boolean alreadyConfigured = CollectionUtils.containsAny(args, logOptions);
-        if (!alreadyConfigured) {
-            args.add("-i");
-        }
-    }
-
-    private void configureJvmArgs(List<String> args) {
-        if(!noDefaultJvmArgs) {
-            String jvmArgs  = "-Dorg.gradle.jvmargs=-ea -XX:MaxPermSize=256m -XX:+HeapDumpOnOutOfMemoryError";
-            if (JavaVersion.current().isJava5()) {
-                jvmArgs = String.format("%s -XX:+CMSPermGenSweepingEnabled -Dcom.sun.management.jmxremote", jvmArgs);
-            }
-            args.add(0, jvmArgs);
-        }
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/EmbeddedDaemonGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/EmbeddedDaemonGradleExecuter.java
deleted file mode 100644
index 9d62926..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/EmbeddedDaemonGradleExecuter.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures;
-
-import org.gradle.StartParameter;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.initialization.BuildClientMetaData;
-import org.gradle.initialization.DefaultCommandLineConverter;
-import org.gradle.launcher.cli.ExecuteBuildAction;
-import org.gradle.launcher.daemon.client.DaemonClient;
-import org.gradle.launcher.daemon.client.EmbeddedDaemonClientServices;
-import org.gradle.launcher.daemon.registry.DaemonRegistry;
-import org.gradle.launcher.exec.BuildActionParameters;
-import org.gradle.launcher.exec.DefaultBuildActionParameters;
-import org.gradle.logging.LoggingManagerInternal;
-import org.gradle.logging.LoggingServiceRegistry;
-import org.gradle.logging.internal.StreamBackedStandardOutputListener;
-
-import java.lang.management.ManagementFactory;
-
-class EmbeddedDaemonGradleExecuter extends AbstractGradleExecuter {
-
-    private final EmbeddedDaemonClientServices daemonClientServices = new EmbeddedDaemonClientServices(LoggingServiceRegistry.newEmbeddableLogging(), false);
-
-    public DaemonRegistry getDaemonRegistry() {
-        return daemonClientServices.get(DaemonRegistry.class);
-    }
-
-    public void assertCanExecute() throws AssertionError {
-    }
-
-    protected ExecutionResult doRun() {
-        return doRun(false);
-    }
-
-    protected ExecutionFailure doRunWithFailure() {
-        return (ExecutionFailure)doRun(true);
-    }
-
-    protected ExecutionResult doRun(boolean expectFailure) {
-        StringBuilder output = new StringBuilder();
-        StringBuilder error = new StringBuilder();
-
-        LoggingManagerInternal loggingManager = createLoggingManager(output, error);
-        loggingManager.start();
-
-        ExecuteBuildAction buildAction = createBuildAction();
-        BuildActionParameters buildActionParameters = createBuildActionParameters();
-        DaemonClient daemonClient = daemonClientServices.get(DaemonClient.class);
-
-        Exception failure = null;
-        try {
-            daemonClient.execute(buildAction, buildActionParameters);
-        } catch (Exception e) {
-            failure = e;
-        } finally {
-            daemonClient.stop();
-            loggingManager.stop();
-        }
-
-        boolean didFail = failure != null;
-        if (expectFailure != didFail) {
-            String didOrDidntSnippet = didFail ? "DID fail" : "DID NOT fail";
-            throw new RuntimeException(String.format("Gradle execution in %s %s with: %nOutput:%n%s%nError:%n%s%n-----%n", getWorkingDir(), didOrDidntSnippet, output, error), failure);
-        }
-
-        if (expectFailure) {
-            return new OutputScrapingExecutionFailure(output.toString(), error.toString());
-        } else {
-            return new OutputScrapingExecutionResult(output.toString(), error.toString());
-        }
-    }
-
-    private LoggingManagerInternal createLoggingManager(StringBuilder output, StringBuilder error) {
-        LoggingManagerInternal loggingManager = daemonClientServices.getLoggingServices().newInstance(LoggingManagerInternal.class);
-        loggingManager.addStandardOutputListener(new StreamBackedStandardOutputListener(output));
-        loggingManager.addStandardErrorListener(new StreamBackedStandardOutputListener(error));
-        return loggingManager;
-    }
-
-    private ExecuteBuildAction createBuildAction() {
-        DefaultCommandLineConverter commandLineConverter = new DefaultCommandLineConverter();
-        StartParameter startParameter = new StartParameter();
-        startParameter.setCurrentDir(getWorkingDir());
-        commandLineConverter.convert(getAllArgs(), startParameter);
-        return new ExecuteBuildAction(startParameter);
-    }
-
-    private BuildActionParameters createBuildActionParameters() {
-        return new DefaultBuildActionParameters(daemonClientServices.get(BuildClientMetaData.class), getStartTime(), System.getProperties(), getEnvironmentVars(), getWorkingDir(), LogLevel.LIFECYCLE);
-    }
-
-    private long getStartTime() {
-        return ManagementFactory.getRuntimeMXBean().getStartTime();
-    }
-}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ExecutionFailure.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ExecutionFailure.java
deleted file mode 100644
index 01bd531..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ExecutionFailure.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures;
-
-import org.hamcrest.Matcher;
-
-public interface ExecutionFailure extends ExecutionResult {
-    ExecutionFailure assertHasLineNumber(int lineNumber);
-
-    ExecutionFailure assertHasFileName(String filename);
-
-    ExecutionFailure assertHasCause(String description);
-
-    ExecutionFailure assertThatCause(Matcher<String> matcher);
-
-    ExecutionFailure assertHasDescription(String context);
-
-    ExecutionFailure assertThatDescription(Matcher<String> matcher);
-
-    ExecutionFailure assertHasNoCause();
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ExecutionResult.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ExecutionResult.java
deleted file mode 100644
index 380ee52..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ExecutionResult.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures;
-
-import java.util.List;
-import java.util.Set;
-
-public interface ExecutionResult {
-    String getOutput();
-
-    String getError();
-
-    ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines);
-
-    /**
-     * Returns the tasks have been executed in order (includes tasks that were skipped). Note: ignores buildSrc tasks.
-     */
-    List<String> getExecutedTasks();
-    
-    /**
-     * Asserts that exactly the given set of tasks have been executed in the given order. Note: ignores buildSrc tasks.
-     */
-    ExecutionResult assertTasksExecuted(String... taskPaths);
-
-    /**
-     * Returns the tasks that were skipped, in an undefined order. Note: ignores buildSrc tasks.
-     */
-    Set<String> getSkippedTasks();
-    
-    /**
-     * Asserts that exactly the given set of tasks have been skipped. Note: ignores buildSrc tasks.
-     */
-    ExecutionResult assertTasksSkipped(String... taskPaths);
-
-    /**
-     * Asserts the given task has been skipped. Note: ignores buildSrc tasks.
-     */
-    ExecutionResult assertTaskSkipped(String taskPath);
-
-    /**
-     * Asserts that exactly the given set of tasks have not been skipped. Note: ignores buildSrc tasks.
-     */
-    ExecutionResult assertTasksNotSkipped(String... taskPaths);
-
-    /**
-     * Asserts that the given task has not been skipped. Note: ignores buildSrc tasks.
-     */
-    ExecutionResult assertTaskNotSkipped(String taskPath);
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkingGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkingGradleExecuter.java
deleted file mode 100644
index 4de7f1a..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkingGradleExecuter.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures;
-
-import org.gradle.internal.Factory;
-import org.gradle.internal.nativeplatform.jna.WindowsHandlesManipulator;
-import org.gradle.internal.os.OperatingSystem;
-import org.gradle.launcher.daemon.registry.DaemonRegistry;
-import org.gradle.launcher.daemon.registry.DaemonRegistryServices;
-import org.gradle.process.internal.ExecHandleBuilder;
-import org.gradle.util.TestFile;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.junit.Assert.fail;
-
-class ForkingGradleExecuter extends AbstractGradleExecuter {
-    private static final Logger LOG = LoggerFactory.getLogger(ForkingGradleExecuter.class);
-    private final TestFile gradleHomeDir;
-
-    public ForkingGradleExecuter(TestFile gradleHomeDir) {
-        this.gradleHomeDir = gradleHomeDir;
-        gradleOpts.add("-ea");
-        //uncomment for debugging
-//        gradleOpts.add("-Xdebug");
-//        gradleOpts.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005");
-    }
-
-    public DaemonRegistry getDaemonRegistry() {
-        return new DaemonRegistryServices(getDaemonBaseDir()).get(DaemonRegistry.class);
-    }
-
-    public void assertCanExecute() throws AssertionError {
-        // Can run any build
-    }
-
-    @Override
-    protected List<String> getAllArgs() {
-        List<String> args = new ArrayList<String>();
-        args.addAll(super.getAllArgs());
-        args.add("--stacktrace");
-        return args;
-    }
-
-    private ExecHandleBuilder createExecHandleBuilder() {
-        if (!gradleHomeDir.isDirectory()) {
-            fail(gradleHomeDir + " is not a directory.\n"
-                    + "If you are running tests from IDE make sure that gradle tasks that prepare the test image were executed. Last time it was 'intTestImage' task.");
-        }
-
-        ExecHandleBuilder builder = new ExecHandleBuilder() {
-            @Override
-            public File getWorkingDir() {
-                // Override this, so that the working directory is not canonicalised. Some int tests require that
-                // the working directory is not canonicalised
-                return ForkingGradleExecuter.this.getWorkingDir();
-            }
-        };
-
-        // Override some of the user's environment
-        builder.environment("GRADLE_HOME", "");
-        builder.environment("JAVA_HOME", getJavaHome());
-        builder.environment("GRADLE_OPTS", formatGradleOpts());
-        builder.environment("JAVA_OPTS", "");
-
-        builder.environment(getAllEnvironmentVars());
-        builder.workingDir(getWorkingDir());
-        builder.setStandardInput(getStdin());
-
-        ExecHandlerConfigurer configurer = OperatingSystem.current().isWindows() ? new WindowsConfigurer()
-                : new UnixConfigurer();
-        configurer.configure(builder);
-
-        builder.args(getAllArgs());
-
-        LOG.info(String.format("Execute in %s with: %s %s", builder.getWorkingDir(), builder.getExecutable(),
-                builder.getArgs()));
-
-        return builder;
-    }
-
-    @Override
-    public GradleHandle doStart() {
-        return createGradleHandle(getDefaultCharacterEncoding(), new Factory<ExecHandleBuilder>() {
-            public ExecHandleBuilder create() {
-                return createExecHandleBuilder();
-            }
-        }).start();
-    }
-
-    protected ForkingGradleHandle createGradleHandle(String encoding, Factory<ExecHandleBuilder> execHandleFactory) {
-        return new ForkingGradleHandle(encoding, execHandleFactory);
-    }
-
-    protected ExecutionResult doRun() {
-        return start().waitForFinish();
-    }
-
-    protected ExecutionFailure doRunWithFailure() {
-        return start().waitForFailure();
-    }
-
-    private String formatGradleOpts() {
-        if (getUserHomeDir() != null) {
-            gradleOpts.add(String.format("-Duser.home=%s", getUserHomeDir()));
-        }
-
-        StringBuilder result = new StringBuilder();
-        for (String gradleOpt : gradleOpts) {
-            if (result.length() > 0) {
-                result.append(" ");
-            }
-            if (gradleOpt.contains(" ")) {
-                assert !gradleOpt.contains("\"");
-                result.append('"');
-                result.append(gradleOpt);
-                result.append('"');
-            } else {
-                result.append(gradleOpt);
-            }
-        }
-
-        result.append(" -Dfile.encoding=");
-        result.append(getDefaultCharacterEncoding());
-        result.append(" -Dorg.gradle.deprecation.trace=true");
-
-        return result.toString();
-    }
-
-    private interface ExecHandlerConfigurer {
-        void configure(ExecHandleBuilder builder);
-    }
-
-    private class WindowsConfigurer implements ExecHandlerConfigurer {
-        public void configure(ExecHandleBuilder builder) {
-            String cmd;
-            if (getExecutable() != null) {
-                cmd = getExecutable().replace('/', File.separatorChar);
-            } else {
-                cmd = "gradle";
-            }
-            builder.executable("cmd");
-            builder.args("/c", cmd);
-            String gradleHome = gradleHomeDir.getAbsolutePath();
-
-            // NOTE: Windows uses Path, but allows asking for PATH, and PATH
-            //       is set within builder object for some things such
-            //       as CommandLineIntegrationTest, try PATH first, and
-            //       then revert to default of Path if null
-            Object path = builder.getEnvironment().get("PATH");
-            if (path == null) {
-                path = builder.getEnvironment().get("Path");
-            }
-            builder.environment("Path", String.format("%s\\bin;%s",
-                                                      gradleHome,
-                                                      path));
-            builder.environment("GRADLE_EXIT_CONSOLE", "true");
-
-            LOG.info("Initializing windows process so that child process will be fully detached...");
-            new WindowsHandlesManipulator().uninheritStandardStreams();
-        }
-    }
-
-    private class UnixConfigurer implements ExecHandlerConfigurer {
-        public void configure(ExecHandleBuilder builder) {
-            if (getExecutable() != null) {
-                File exe = new File(getExecutable());
-                if (exe.isAbsolute()) {
-                    builder.executable(exe.getAbsolutePath());
-                } else {
-                    builder.executable(String.format("%s/%s", getWorkingDir().getAbsolutePath(), getExecutable()));
-                }
-            } else {
-                builder.executable(String.format("%s/bin/gradle", gradleHomeDir.getAbsolutePath()));
-            }
-        }
-    }
-
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkingGradleHandle.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkingGradleHandle.java
deleted file mode 100644
index 5e066b4..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkingGradleHandle.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures;
-
-import org.apache.commons.io.output.CloseShieldOutputStream;
-import org.apache.commons.io.output.TeeOutputStream;
-import org.gradle.internal.Factory;
-import org.gradle.internal.UncheckedException;
-import org.gradle.process.ExecResult;
-import org.gradle.process.internal.AbstractExecHandleBuilder;
-import org.gradle.process.internal.ExecHandle;
-import org.gradle.process.internal.ExecHandleState;
-
-import java.io.ByteArrayOutputStream;
-import java.io.UnsupportedEncodingException;
-
-class ForkingGradleHandle extends OutputScrapingGradleHandle {
-    final private Factory<? extends AbstractExecHandleBuilder> execHandleFactory;
-
-    final private ByteArrayOutputStream standardOutput = new ByteArrayOutputStream();
-    final private ByteArrayOutputStream errorOutput = new ByteArrayOutputStream();
-
-    private ExecHandle execHandle;
-    private final String outputEncoding;
-
-    public ForkingGradleHandle(String outputEncoding, Factory<? extends AbstractExecHandleBuilder> execHandleFactory) {
-        this.execHandleFactory = execHandleFactory;
-        this.outputEncoding = outputEncoding;
-    }
-
-    public String getStandardOutput() {
-        try {
-            return standardOutput.toString(outputEncoding);
-        } catch (UnsupportedEncodingException e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-    }
-
-    public String getErrorOutput() {
-        try {
-            return errorOutput.toString(outputEncoding);
-        } catch (UnsupportedEncodingException e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-    }
-
-    public GradleHandle start() {
-        if (execHandle != null) {
-            throw new IllegalStateException("you have already called start() on this handle");
-        }
-
-        AbstractExecHandleBuilder execBuilder = execHandleFactory.create();
-        execBuilder.setStandardOutput(new CloseShieldOutputStream(new TeeOutputStream(System.out, standardOutput)));
-        execBuilder.setErrorOutput(new CloseShieldOutputStream(new TeeOutputStream(System.err, errorOutput)));
-        execHandle = execBuilder.build();
-
-        execHandle.start();
-
-        return this;
-    }
-
-    public GradleHandle abort() {
-        getExecHandle().abort();
-        return this;
-    }
-
-    public boolean isRunning() {
-        return execHandle != null && execHandle.getState() == ExecHandleState.STARTED;
-    }
-
-    protected ExecHandle getExecHandle() {
-        if (execHandle == null) {
-            throw new IllegalStateException("you must call start() before calling this method");
-        }
-
-        return execHandle;
-    }
-
-    public ExecutionResult waitForFinish() {
-        return waitForStop(false);
-    }
-
-    public ExecutionFailure waitForFailure() {
-        return (ExecutionFailure) waitForStop(true);
-    }
-
-    protected ExecutionResult waitForStop(boolean expectFailure) {
-        ExecHandle execHandle = getExecHandle();
-        ExecResult execResult = execHandle.waitForFinish();
-        execResult.rethrowFailure(); // nop if all ok
-
-        String output = getStandardOutput();
-        String error = getErrorOutput();
-
-        boolean didFail = execResult.getExitValue() != 0;
-        if (didFail != expectFailure) {
-            String message = String.format("Gradle execution %s in %s with: %s %s%nOutput:%n%s%n-----%nError:%n%s%n-----%n",
-                    expectFailure ? "did not fail" : "failed", execHandle.getDirectory(), execHandle.getCommand(), execHandle.getArguments(), output, error);
-            throw new UnexpectedBuildFailure(message);
-        }
-        return expectFailure ? toExecutionFailure(output, error) : toExecutionResult(output, error);
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleBackedArtifactBuilder.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleBackedArtifactBuilder.java
deleted file mode 100644
index c022564..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleBackedArtifactBuilder.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures;
-
-import org.apache.commons.io.FilenameUtils;
-import org.gradle.util.TestFile;
-
-import java.io.File;
-
-public class GradleBackedArtifactBuilder implements ArtifactBuilder {
-    private final GradleExecuter executer;
-    private final TestFile rootDir;
-
-    public GradleBackedArtifactBuilder(GradleExecuter executer, File rootDir) {
-        this.executer = executer;
-        this.rootDir = new TestFile(rootDir);
-    }
-
-    public TestFile sourceFile(String path) {
-        return rootDir.file("src/main/java", path);
-    }
-
-    public TestFile resourceFile(String path) {
-        return rootDir.file("src/main/resources", path);
-    }
-
-    public void buildJar(File jarFile) {
-        rootDir.file("build.gradle").writelns(
-                "apply plugin: 'java'",
-                "dependencies { compile gradleApi() }",
-                String.format("jar.destinationDir = file('%s')", FilenameUtils.separatorsToUnix(jarFile.getParent())),
-                String.format("jar.archiveName = '%s'", jarFile.getName())
-        );
-        executer.inDirectory(rootDir).withTasks("clean", "jar").run();
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistribution.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistribution.java
deleted file mode 100644
index 41a4bee..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistribution.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures;
-
-import org.gradle.internal.jvm.Jvm;
-import org.gradle.internal.os.OperatingSystem;
-import org.gradle.util.GradleVersion;
-import org.gradle.util.TemporaryFolder;
-import org.gradle.util.TestFile;
-import org.gradle.util.TestFileContext;
-import org.junit.rules.MethodRule;
-import org.junit.runners.model.FrameworkMethod;
-import org.junit.runners.model.Statement;
-
-import java.io.File;
-
-/**
- * Provides access to a Gradle distribution for integration testing.
- */
-public class GradleDistribution implements MethodRule, TestFileContext, BasicGradleDistribution {
-    private static final TestFile USER_HOME_DIR;
-    private static final TestFile GRADLE_HOME_DIR;
-    private static final TestFile SAMPLES_DIR;
-    private static final TestFile USER_GUIDE_OUTPUT_DIR;
-    private static final TestFile USER_GUIDE_INFO_DIR;
-    private static final TestFile DISTS_DIR;
-    private static final TestFile LIBS_REPO;
-    private static final TestFile DAEMON_BASE_DIR;
-
-    private final TemporaryFolder temporaryFolder = new TemporaryFolder();
-    private TestFile userHome;
-    private boolean usingIsolatedDaemons;
-
-    static {
-        USER_HOME_DIR = file("integTest.gradleUserHomeDir", "intTestHomeDir").file("worker-1");
-        GRADLE_HOME_DIR = file("integTest.gradleHomeDir", null);
-        SAMPLES_DIR = file("integTest.samplesdir", String.format("%s/samples", GRADLE_HOME_DIR));
-        USER_GUIDE_OUTPUT_DIR = file("integTest.userGuideOutputDir",
-                "subprojects/docs/src/samples/userguideOutput");
-        USER_GUIDE_INFO_DIR = file("integTest.userGuideInfoDir", "subprojects/docs/build/src");
-        DISTS_DIR = file("integTest.distsDir", "build/distributions");
-        LIBS_REPO = file("integTest.libsRepo", "build/repo");
-        DAEMON_BASE_DIR = file("org.gradle.integtest.daemon.registry", "build/daemon");
-    }
-
-    public GradleDistribution() {
-        this.userHome = USER_HOME_DIR;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("Gradle %s", GradleVersion.current().getVersion());
-    }
-
-    public boolean worksWith(Jvm jvm) {
-        // Works with anything >= Java 5
-        return jvm.getJavaVersion().isJava5Compatible();
-    }
-
-    public boolean worksWith(OperatingSystem os) {
-        return true;
-    }
-
-    public boolean isDaemonSupported() {
-        return true;
-    }
-
-    public boolean isDaemonIdleTimeoutConfigurable() {
-        return true;
-    }
-
-    public boolean isOpenApiSupported() {
-        return true;
-    }
-
-    public boolean isToolingApiSupported() {
-        return true;
-    }
-
-    public boolean isMultiProcessSafeCache() {
-        return true;
-    }
-
-    public boolean wrapperCanExecute(String version) {
-        // Current wrapper works with anything > 0.8
-        return GradleVersion.version(version).compareTo(GradleVersion.version("0.8")) > 0;
-    }
-
-    public TestFile getDaemonBaseDir() {
-        if (usingIsolatedDaemons) {
-            return getTestDir().file("daemon");
-        } else {
-            return DAEMON_BASE_DIR;
-        }
-    }
-
-    public void requireOwnUserHomeDir() {
-        userHome = getTestDir().file("user-home");
-    }
-
-    public boolean isUsingIsolatedDaemons() {
-        return usingIsolatedDaemons;
-    }
-
-    public void requireIsolatedDaemons() {
-        this.usingIsolatedDaemons = true;
-    }
-
-    public Statement apply(Statement base, FrameworkMethod method, Object target) {
-        return temporaryFolder.apply(base, method, target);
-    }
-
-    private static TestFile file(String propertyName, String defaultFile) {
-        String path = System.getProperty(propertyName, defaultFile);
-        if (path == null) {
-            throw new RuntimeException(String.format("You must set the '%s' property to run the integration tests. The default passed was: '%s'",
-                    propertyName, defaultFile));
-        }
-        return new TestFile(new File(path));
-    }
-
-    /**
-     * The user home dir used for the current test. This is usually shared with other tests unless
-     * {@link #requireOwnUserHomeDir()} is called.
-     */
-    public TestFile getUserHomeDir() {
-        return userHome;
-    }
-
-    /**
-     * The distribution for the current test. This is usually shared with other tests.
-     */
-    public TestFile getGradleHomeDir() {
-        return GRADLE_HOME_DIR;
-    }
-
-    public String getVersion() {
-        return GradleVersion.current().getVersion();
-    }
-
-    public TestFile getBinDistribution() {
-        return getDistributionsDir().file(String.format("gradle-%s-bin.zip", getVersion()));
-    }
-
-    /**
-     * The samples from the distribution. These are usually shared with other tests.
-     */
-    public TestFile getSamplesDir() {
-        return SAMPLES_DIR;
-    }
-
-    public TestFile getUserGuideInfoDir() {
-        return USER_GUIDE_INFO_DIR;
-    }
-
-    public TestFile getUserGuideOutputDir() {
-        return USER_GUIDE_OUTPUT_DIR;
-    }
-
-    /**
-     * The directory containing the distribution Zips
-     */
-    public TestFile getDistributionsDir() {
-        return DISTS_DIR;
-    }
-
-    public TestFile getLibsRepo() {
-        return LIBS_REPO;
-    }
-
-    public TestFile getPreviousVersionsDir() {
-        return USER_HOME_DIR.getParentFile().file("previousVersion");
-    }
-
-    /**
-     * Returns true if the given file is either part of the distributions, samples, or test files.
-     */
-    public boolean isFileUnderTest(File file) {
-        return GRADLE_HOME_DIR.isSelfOrDescendent(file)
-                || SAMPLES_DIR.isSelfOrDescendent(file)
-                || getTestDir().isSelfOrDescendent(file)
-                || getUserHomeDir().isSelfOrDescendent(file);
-    }
-
-    /**
-     * Returns a scratch-pad directory for the current test. This directory is not shared with any other tests.
-     */
-    public TestFile getTestDir() {
-        return temporaryFolder.getDir();
-    }
-
-    public TemporaryFolder getTemporaryFolder() {
-        return temporaryFolder;
-    }
-
-    /**
-     * Returns a previous version of Gradle.
-     *
-     * @param version The Gradle version
-     * @return An executer
-     */
-    public BasicGradleDistribution previousVersion(String version) {
-        if (version.equals(this.getVersion())) {
-            return this;
-        }
-        return new PreviousGradleVersionExecuter(this, version);
-    }
-
-    public GradleDistributionExecuter executer() {
-        return new GradleDistributionExecuter(this);
-    }
-
-    /**
-     * Returns a scratch-pad file for the current test. Equivalent to getTestDir().file(path)
-     */
-    public TestFile file(Object... path) {
-        return getTestDir().file(path);
-    }
-
-    /**
-     * Returns a scratch-pad file for the current test. Equivalent to getTestDir().file(path)
-     */
-    public TestFile testFile(Object... path) {
-        return getTestDir().file(path);
-    }
-}
-
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java
deleted file mode 100644
index 706bc5a..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures;
-
-import org.gradle.util.DeprecationLogger;
-import org.gradle.util.TestFile;
-import org.gradle.util.TextUtil;
-import org.junit.rules.MethodRule;
-import org.junit.runners.model.FrameworkMethod;
-import org.junit.runners.model.Statement;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.gradle.util.Matchers.containsLine;
-import static org.gradle.util.Matchers.matchesRegexp;
-
-/**
- * A JUnit rule which provides a {@link GradleExecuter} implementation that executes Gradle using a given {@link
- * GradleDistribution}. If not supplied in the constructor, this rule locates a field on the test object with type
- * {@link GradleDistribution}.
- *
- * By default, this executer will execute Gradle in a forked process. There is a system property which enables executing
- * Gradle in the current process.
- */
-public class GradleDistributionExecuter extends AbstractDelegatingGradleExecuter implements MethodRule {
-    private static final String EXECUTER_SYS_PROP = "org.gradle.integtest.executer";
-    private static final String UNKNOWN_OS_SYS_PROP = "org.gradle.integtest.unknownos";
-    private static final int DEFAULT_DAEMON_IDLE_TIMEOUT_SECS = 2 * 60;
-
-    private GradleDistribution dist;
-    private boolean workingDirSet;
-    private boolean gradleUserHomeDirSet;
-    private boolean deprecationChecksOn = true;
-    private boolean stackTraceChecksOn = true;
-    private Executer executerType;
-
-    private boolean allowExtraLogging = true;
-
-    public enum Executer {
-        embedded(false),
-        forking(true),
-        daemon(true),
-        embeddedDaemon(false),
-        parallel(true, true);
-
-        final public boolean forks;
-        final public boolean executeParallel;
-
-        Executer(boolean forks) {
-            this(forks, false);
-        }
-
-        Executer(boolean forks, boolean parallel) {
-            this.forks = forks;
-            this.executeParallel = parallel;
-        }
-    }
-
-    public static Executer getSystemPropertyExecuter() {
-        return Executer.valueOf(System.getProperty(EXECUTER_SYS_PROP, Executer.forking.toString()));
-    }
-
-    public GradleDistributionExecuter() {
-        this(getSystemPropertyExecuter());
-    }
-
-    public GradleDistributionExecuter(Executer executerType) {
-        this.executerType = executerType;
-    }
-
-    public GradleDistributionExecuter(GradleDistribution dist) {
-        this(getSystemPropertyExecuter(), dist);
-    }
-
-    public GradleDistributionExecuter(Executer executerType, GradleDistribution dist) {
-        this(executerType);
-        this.dist = dist;
-        reset();
-    }
-
-    public Statement apply(Statement base, final FrameworkMethod method, Object target) {
-        if (dist == null) {
-            dist = RuleHelper.getField(target, GradleDistribution.class);
-        }
-        beforeExecute(new RedirectMavenCentral(dist.getTemporaryFolder()));
-        return base;
-    }
-
-    @Override
-    public GradleDistributionExecuter reset() {
-        super.reset();
-        workingDirSet = false;
-        gradleUserHomeDirSet = false;
-        deprecationChecksOn = true;
-        stackTraceChecksOn = true;
-        DeprecationLogger.reset();
-        return this;
-    }
-
-    @Override
-    public GradleDistributionExecuter inDirectory(File directory) {
-        super.inDirectory(directory);
-        workingDirSet = true;
-        return this;
-    }
-
-    @Override
-    public GradleDistributionExecuter withGradleUserHomeDir(File userHomeDir) {
-        super.withGradleUserHomeDir(userHomeDir);
-        gradleUserHomeDirSet = true;
-        return this;
-    }
-
-    public GradleDistributionExecuter withDeprecationChecksDisabled() {
-        deprecationChecksOn = false;
-        // turn off stack traces too
-        stackTraceChecksOn = false;
-        return this;
-    }
-
-    public GradleDistributionExecuter withStackTraceChecksDisabled() {
-        stackTraceChecksOn = false;
-        return this;
-    }
-    
-    public GradleDistributionExecuter withForkingExecuter() {
-        if (!executerType.forks) {
-            executerType = Executer.forking;
-        }
-        return this;
-    }
-
-    protected <T extends ExecutionResult> T checkResult(T result) {
-        if (stackTraceChecksOn) {
-            // Assert that nothing unexpected was logged
-            assertOutputHasNoStackTraces(result);
-            assertErrorHasNoStackTraces(result);
-        }
-        if (deprecationChecksOn) {
-            assertOutputHasNoDeprecationWarnings(result);
-        }
-
-        if (getExecutable() == null) {
-            // Assert that no temp files are left lying around
-            // Note: don't do this if a custom executable is used, as we don't know (and probably don't care) whether the executable cleans up or not
-            List<String> unexpectedFiles = new ArrayList<String>();
-            for (File file : getTmpDir().listFiles()) {
-                if (!file.getName().matches("maven-artifact\\d+.tmp")) {
-                    unexpectedFiles.add(file.getName());
-                }
-            }
-//            Assert.assertThat(unexpectedFiles, Matchers.isEmpty());
-        }
-
-        return result;
-    }
-
-    private void assertOutputHasNoStackTraces(ExecutionResult result) {
-        assertNoStackTraces(result.getOutput(), "Standard output");
-    }
-
-    public void assertErrorHasNoStackTraces(ExecutionResult result) {
-        String error = result.getError();
-        if (result instanceof ExecutionFailure) {
-            // Axe everything after the expected exception
-            int pos = error.indexOf("* Exception is:" + TextUtil.getPlatformLineSeparator());
-            if (pos >= 0) {
-                error = error.substring(0, pos);
-            }
-        }
-        assertNoStackTraces(error, "Standard error");
-    }
-
-    public void assertOutputHasNoDeprecationWarnings(ExecutionResult result) {
-        assertNoDeprecationWarnings(result.getOutput(), "Standard output");
-        assertNoDeprecationWarnings(result.getError(), "Standard error");
-    }
-
-    private void assertNoDeprecationWarnings(String output, String displayName) {
-        boolean javacWarning = containsLine(matchesRegexp(".*use(s)? or override(s)? a deprecated API\\.")).matches(output);
-        boolean deprecationWarning = containsLine(matchesRegexp(".* deprecated.*")).matches(output);
-        if (deprecationWarning && !javacWarning) {
-            throw new AssertionError(String.format("%s contains a deprecation warning:%n=====%n%s%n=====%n", displayName, output));
-        }
-    }
-
-    private void assertNoStackTraces(String output, String displayName) {
-        if (containsLine(matchesRegexp("\\s+(at\\s+)?[\\w.$_]+\\([\\w._]+:\\d+\\)")).matches(output)) {
-            throw new AssertionError(String.format("%s contains an unexpected stack trace:%n=====%n%s%n=====%n", displayName, output));
-        }
-    }
-
-    /**
-     * set true to allow the executer to increase the log level if necessary
-     * to help out debugging. Set false to make the executer never update the log level.
-     */
-    public GradleDistributionExecuter setAllowExtraLogging(boolean allowExtraLogging) {
-        this.allowExtraLogging = allowExtraLogging;
-        return this;
-    }
-
-    protected GradleExecuter configureExecuter() {
-        if (!workingDirSet) {
-            inDirectory(dist.getTestDir());
-        }
-        if (!gradleUserHomeDirSet) {
-            withGradleUserHomeDir(dist.getUserHomeDir());
-        }
-        if (getDaemonIdleTimeoutSecs() == null) {
-            if (dist.isUsingIsolatedDaemons() || getDaemonBaseDir() != null) {
-                withDaemonIdleTimeoutSecs(20);
-            } else {
-                withDaemonIdleTimeoutSecs(DEFAULT_DAEMON_IDLE_TIMEOUT_SECS);
-            }
-        }
-        if (getDaemonBaseDir() == null) {
-            withDaemonBaseDir(dist.getDaemonBaseDir());
-        }
-
-        if (!getClass().desiredAssertionStatus()) {
-            throw new RuntimeException("Assertions must be enabled when running integration tests.");
-        }
-
-        GradleExecuter gradleExecuter = createExecuter(executerType);
-        configureExecuter(gradleExecuter);
-        try {
-            gradleExecuter.assertCanExecute();
-        } catch (AssertionError assertionError) {
-            gradleExecuter = new ForkingGradleExecuter(dist.getGradleHomeDir());
-            configureExecuter(gradleExecuter);
-        }
-
-        return gradleExecuter;
-    }
-
-    private void configureExecuter(GradleExecuter gradleExecuter) {
-        copyTo(gradleExecuter);
-
-        configureTmpDir(gradleExecuter);
-        configureForSettingsFile(gradleExecuter);
-
-        if (System.getProperty(UNKNOWN_OS_SYS_PROP) != null) {
-            gradleExecuter.withGradleOpts("-Dos.arch=unknown architecture", "-Dos.name=unknown operating system", "-Dos.version=unknown version");
-        }
-    }
-
-    private GradleExecuter createExecuter(Executer executerType) {
-        switch (executerType) {
-            case embeddedDaemon:
-                return new EmbeddedDaemonGradleExecuter();
-            case embedded:
-                return new InProcessGradleExecuter();
-            case daemon:
-                return new DaemonGradleExecuter(dist, !isQuiet() && allowExtraLogging, noDefaultJvmArgs);
-            case parallel:
-                return new ParallelForkingGradleExecuter(dist.getGradleHomeDir());
-            case forking:
-                return new ForkingGradleExecuter(dist.getGradleHomeDir());
-            default:
-                throw new RuntimeException("Not a supported executer type: " + executerType);
-        }
-    }
-
-    private void configureTmpDir(GradleExecuter gradleExecuter) {
-        TestFile tmpDir = getTmpDir();
-        tmpDir.createDir();
-        gradleExecuter.withGradleOpts(String.format("-Djava.io.tmpdir=%s", tmpDir));
-    }
-
-    private void configureForSettingsFile(GradleExecuter gradleExecuter) {
-        boolean settingsFound = false;
-        for (
-                TestFile dir = new TestFile(getWorkingDir()); dir != null && dist.isFileUnderTest(dir) && !settingsFound;
-                dir = dir.getParentFile()) {
-            if (dir.file("settings.gradle").isFile()) {
-                settingsFound = true;
-            }
-        }
-        if (settingsFound) {
-            gradleExecuter.withSearchUpwards();
-        }
-    }
-
-    private TestFile getTmpDir() {
-        return dist.getTestDir().file("tmp");
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleExecuter.java
deleted file mode 100644
index 74bf7b6..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleExecuter.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures;
-
-import groovy.lang.Closure;
-import org.gradle.api.Action;
-import org.gradle.launcher.daemon.registry.DaemonRegistry;
-
-import java.io.File;
-import java.io.InputStream;
-import java.util.List;
-import java.util.Map;
-
-public interface GradleExecuter {
-    /**
-     * Sets the working directory to use. Defaults to the test's temporary directory.
-     */
-    GradleExecuter inDirectory(File directory);
-
-    /**
-     * Enables search upwards. Defaults to false.
-     */
-    GradleExecuter withSearchUpwards();
-
-    /**
-     * Sets the task names to execute. Defaults to an empty list.
-     */
-    GradleExecuter withTasks(String... names);
-
-    /**
-     * Sets the task names to execute. Defaults to an empty list.
-     */
-    GradleExecuter withTasks(List<String> names);
-
-    GradleExecuter withTaskList();
-
-    GradleExecuter withDependencyList();
-
-    GradleExecuter withQuietLogging();
-
-    /**
-     * Sets the additional command-line arguments to use when executing the build. Defaults to an empty list.
-     */
-    GradleExecuter withArguments(String... args);
-
-    /**
-     * Sets the additional command-line arguments to use when executing the build. Defaults to an empty list.
-     */
-    GradleExecuter withArguments(List<String> args);
-
-    /**
-     * Adds an additional command-line argument to use when executing the build.
-     */
-    GradleExecuter withArgument(String arg);
-
-    /**
-     * Sets the environment variables to use when executing the build. Defaults to the environment of this process.
-     */
-    GradleExecuter withEnvironmentVars(Map<String, ?> environment);
-
-    GradleExecuter usingSettingsFile(File settingsFile);
-
-    GradleExecuter usingInitScript(File initScript);
-
-    /**
-     * Uses the given project directory
-     */
-    GradleExecuter usingProjectDirectory(File projectDir);
-
-    /**
-     * Uses the given build script
-     */
-    GradleExecuter usingBuildScript(File buildScript);
-
-    /**
-     * Sets the user's home dir to use when running the build. Implementations are not 100% accurate.
-     */
-    GradleExecuter withUserHomeDir(File userHomeDir);
-
-    /**
-     * Sets the <em>Gradle</em> user home dir. Setting to null requests that the executer use the real default Gradle user home dir rather than the
-     * default used for testing.
-     */
-    GradleExecuter withGradleUserHomeDir(File userHomeDir);
-
-    /**
-     * Sets the java home dir. Setting to null requests that the executer use the real default java home dir rather than the default used for testing.
-     */
-    GradleExecuter withJavaHome(File userHomeDir);
-
-    /**
-     * Sets the executable to use. Set to null to use the read default executable (if any) rather than the default used for testing.
-     */
-    GradleExecuter usingExecutable(String script);
-
-    /**
-     * Sets the stdin to use for the build. Defaults to an empty string.
-     */
-    GradleExecuter withStdIn(String text);
-
-    /**
-     * Sets the stdin to use for the build. Defaults to an empty string.
-     */
-    GradleExecuter withStdIn(InputStream stdin);
-
-    /**
-     * Specifies that the executer should not set any default jvm args.
-     */
-    GradleExecuter withNoDefaultJvmArgs();
-
-    /**
-     * Executes the requested build, asserting that the build succeeds. Resets the configuration of this executer.
-     *
-     * @return The result.
-     */
-    ExecutionResult run();
-
-    /**
-     * Executes the requested build, asserting that the build fails. Resets the configuration of this executer.
-     *
-     * @return The result.
-     */
-    ExecutionFailure runWithFailure();
-
-    /**
-     * Provides a daemon registry for any daemons started by this executer, which may be none.
-     *
-     * @return the daemon registry, never null.
-     */
-    DaemonRegistry getDaemonRegistry();
-
-    /**
-     * Starts executing the build asynchronously.
-     *
-     * @return the handle, never null.
-     */
-    GradleHandle start();
-
-    /**
-     * Adds options that should be used to start the JVM, if a JVM is to be started. Ignored if not.
-     *
-     * @param gradleOpts the jvm opts
-     *
-     * @return this executer
-     */
-    GradleExecuter withGradleOpts(String ... gradleOpts);
-
-    /**
-     * Sets the default character encoding to use.
-     *
-     * Only makes sense for forking executers.
-     *
-     * @return this executer
-     */
-    GradleExecuter withDefaultCharacterEncoding(String defaultCharacterEncoding);
-
-    /**
-     * Set the number of seconds an idle daemon should live for.
-     *
-     * @param secs
-     *
-     * @return this executer
-     */
-    GradleExecuter withDaemonIdleTimeoutSecs(int secs);
-
-    /**
-     * Set the working space for the daemon and launched daemons
-     *
-     * @param baseDir
-     *
-     * @return this executer
-     */
-    GradleExecuter withDaemonBaseDir(File baseDir);
-
-    /**
-     * Asserts that this executer will be able to run a build, given its current configuration.
-     *
-     * @throws AssertionError When this executer will not be able to run a build.
-     */
-    void assertCanExecute() throws AssertionError;
-
-    /**
-     * Adds an action to be called immediately before execution, to allow extra configuration to be injected.
-     */
-    void beforeExecute(Action<? super GradleExecuter> action);
-
-    /**
-     * Adds an action to be called immediately before execution, to allow extra configuration to be injected.
-     */
-    void beforeExecute(Closure action);
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleHandle.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleHandle.java
deleted file mode 100644
index 688ebde..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleHandle.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures;
-
-public interface GradleHandle {
-    String getStandardOutput();
-    String getErrorOutput();
-
-    GradleHandle abort();
-
-    ExecutionResult waitForFinish();
-    ExecutionFailure waitForFailure();
-
-    boolean isRunning();
-}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/IgnoreVersions.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/IgnoreVersions.java
new file mode 100644
index 0000000..0fc1bcb
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/IgnoreVersions.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures;
+
+import groovy.lang.Closure;
+
+import java.lang.annotation.*;
+
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.TYPE)
+ at Inherited
+public @interface IgnoreVersions {
+    Class<Closure> value();
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/InProcessGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/InProcessGradleExecuter.java
deleted file mode 100644
index 89e68af..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/InProcessGradleExecuter.java
+++ /dev/null
@@ -1,387 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures;
-
-import org.gradle.BuildListener;
-import org.gradle.BuildResult;
-import org.gradle.GradleLauncher;
-import org.gradle.StartParameter;
-import org.gradle.api.GradleException;
-import org.gradle.api.Task;
-import org.gradle.api.execution.TaskExecutionGraph;
-import org.gradle.api.execution.TaskExecutionGraphListener;
-import org.gradle.api.execution.TaskExecutionListener;
-import org.gradle.api.initialization.Settings;
-import org.gradle.api.internal.LocationAwareException;
-import org.gradle.api.internal.classpath.DefaultModuleRegistry;
-import org.gradle.api.internal.file.IdentityFileResolver;
-import org.gradle.api.invocation.Gradle;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.api.logging.StandardOutputListener;
-import org.gradle.api.tasks.TaskState;
-import org.gradle.cli.CommandLineParser;
-import org.gradle.execution.MultipleBuildFailures;
-import org.gradle.initialization.DefaultCommandLineConverter;
-import org.gradle.initialization.DefaultGradleLauncherFactory;
-import org.gradle.internal.Factory;
-import org.gradle.internal.jvm.Jvm;
-import org.gradle.internal.nativeplatform.ProcessEnvironment;
-import org.gradle.internal.nativeplatform.services.NativeServices;
-import org.gradle.launcher.Main;
-import org.gradle.launcher.daemon.registry.DaemonRegistry;
-import org.gradle.process.internal.JavaExecHandleBuilder;
-import org.gradle.util.DeprecationLogger;
-import org.hamcrest.Matcher;
-
-import java.io.File;
-import java.io.InputStream;
-import java.io.StringWriter;
-import java.nio.charset.Charset;
-import java.util.*;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.CopyOnWriteArraySet;
-
-import static org.gradle.util.Matchers.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-class InProcessGradleExecuter extends AbstractGradleExecuter {
-    private final ProcessEnvironment processEnvironment = NativeServices.getInstance().get(ProcessEnvironment.class);
-
-    @Override
-    protected ExecutionResult doRun() {
-        OutputListenerImpl outputListener = new OutputListenerImpl();
-        OutputListenerImpl errorListener = new OutputListenerImpl();
-        BuildListenerImpl buildListener = new BuildListenerImpl();
-        BuildResult result = doRun(outputListener, errorListener, buildListener);
-        try {
-            result.rethrowFailure();
-        } catch (Exception e) {
-            throw new UnexpectedBuildFailure(e);
-        }
-        return new InProcessExecutionResult(buildListener.executedTasks, buildListener.skippedTasks,
-                outputListener.toString(), errorListener.toString());
-    }
-
-    @Override
-    protected ExecutionFailure doRunWithFailure() {
-        OutputListenerImpl outputListener = new OutputListenerImpl();
-        OutputListenerImpl errorListener = new OutputListenerImpl();
-        BuildListenerImpl buildListener = new BuildListenerImpl();
-        try {
-            doRun(outputListener, errorListener, buildListener).rethrowFailure();
-            throw new AssertionError("expected build to fail but it did not.");
-        } catch (GradleException e) {
-            return new InProcessExecutionFailure(buildListener.executedTasks, buildListener.skippedTasks,
-                    outputListener.writer.toString(), errorListener.writer.toString(), e);
-        }
-    }
-
-    @Override
-    protected GradleHandle doStart() {
-        return new ForkingGradleHandle(getDefaultCharacterEncoding(), new Factory<JavaExecHandleBuilder>() {
-            public JavaExecHandleBuilder create() {
-                JavaExecHandleBuilder builder = new JavaExecHandleBuilder(new IdentityFileResolver());
-                builder.workingDir(getWorkingDir());
-                Set<File> classpath = new DefaultModuleRegistry().getFullClasspath();
-                builder.classpath(classpath);
-                builder.setMain(Main.class.getName());
-                builder.args(getAllArgs());
-                return builder;
-            }
-        }).start();
-    }
-
-    private BuildResult doRun(final OutputListenerImpl outputListener, OutputListenerImpl errorListener,
-                              BuildListenerImpl listener) {
-        InputStream originalStdIn = System.in;
-        System.setIn(getStdin());
-        
-        File userDir = new File(System.getProperty("user.dir"));
-        StartParameter parameter = new StartParameter();
-        parameter.setLogLevel(LogLevel.INFO);
-        parameter.setSearchUpwards(true);
-        parameter.setCurrentDir(getWorkingDir());
-
-        CommandLineParser parser = new CommandLineParser();
-        DefaultCommandLineConverter converter = new DefaultCommandLineConverter();
-        converter.configure(parser);
-        converter.convert(parser.parse(getAllArgs()), parameter);
-
-        Properties originalSysProperties = new Properties();
-        originalSysProperties.putAll(System.getProperties());
-        processEnvironment.maybeSetProcessDir(getWorkingDir());
-        Map<String, String> previousEnv = new HashMap<String, String>();
-        for (Map.Entry<String, String> entry : getEnvironmentVars().entrySet()) {
-            previousEnv.put(entry.getKey(), System.getenv(entry.getKey()));
-            processEnvironment.maybeSetEnvironmentVariable(entry.getKey(), entry.getValue());
-        }
-        if (getUserHomeDir() != null) {
-            System.setProperty("user.home", getUserHomeDir().getPath());
-        }
-
-        DefaultGradleLauncherFactory factory = (DefaultGradleLauncherFactory) GradleLauncher.getFactory();
-        factory.addListener(listener);
-        GradleLauncher gradleLauncher = GradleLauncher.newInstance(parameter);
-        gradleLauncher.addStandardOutputListener(outputListener);
-        gradleLauncher.addStandardErrorListener(errorListener);
-        try {
-            return gradleLauncher.run();
-        } finally {
-            System.setProperties(originalSysProperties);
-            processEnvironment.maybeSetProcessDir(userDir);
-            for (Map.Entry<String, String> entry : previousEnv.entrySet()) {
-                String oldValue = entry.getValue();
-                if (oldValue != null) {
-                    processEnvironment.maybeSetEnvironmentVariable(entry.getKey(), oldValue);
-                } else {
-                    processEnvironment.maybeRemoveEnvironmentVariable(entry.getKey());
-                }
-            }
-            factory.removeListener(listener);
-            System.setIn(originalStdIn);
-        }
-    }
-
-    public DaemonRegistry getDaemonRegistry() {
-        throw new UnsupportedOperationException();
-    }
-
-    public void assertCanExecute() {
-        assertNull(getExecutable());
-        assertEquals(getJavaHome(), Jvm.current().getJavaHome());
-        assertEquals(getDefaultCharacterEncoding(), Charset.defaultCharset().name());
-    }
-
-    private static class BuildListenerImpl implements TaskExecutionGraphListener, BuildListener {
-        private final List<String> executedTasks = new CopyOnWriteArrayList<String>();
-        private final Set<String> skippedTasks = new CopyOnWriteArraySet<String>();
-
-        public void graphPopulated(TaskExecutionGraph graph) {
-            List<Task> planned = new ArrayList<Task>(graph.getAllTasks());
-            graph.addTaskExecutionListener(new TaskListenerImpl(planned, executedTasks, skippedTasks));
-        }
-
-        public void buildStarted(Gradle gradle) {
-            DeprecationLogger.setLogTrace(true);
-        }
-
-        public void settingsEvaluated(Settings settings) {
-
-        }
-
-        public void projectsLoaded(Gradle gradle) {
-
-        }
-
-        public void projectsEvaluated(Gradle gradle) {
-
-        }
-
-        public void buildFinished(BuildResult result) {
-            DeprecationLogger.setLogTrace(false);
-        }
-    }
-
-    private static class OutputListenerImpl implements StandardOutputListener {
-        private StringWriter writer = new StringWriter();
-
-        @Override
-        public String toString() {
-            return writer.toString();
-        }
-
-        public void onOutput(CharSequence output) {
-            writer.append(output);
-        }
-    }
-
-    private static class TaskListenerImpl implements TaskExecutionListener {
-        private final List<Task> planned;
-        private final List<String> executedTasks;
-        private final Set<String> skippedTasks;
-
-        public TaskListenerImpl(List<Task> planned, List<String> executedTasks, Set<String> skippedTasks) {
-            this.planned = planned;
-            this.executedTasks = executedTasks;
-            this.skippedTasks = skippedTasks;
-        }
-
-        public void beforeExecute(Task task) {
-            assertTrue(planned.contains(task));
-
-            String taskPath = path(task);
-            if (taskPath.startsWith(":buildSrc:")) {
-                return;
-            }
-
-            executedTasks.add(taskPath);
-        }
-
-        public void afterExecute(Task task, TaskState state) {
-            String taskPath = path(task);
-            if (taskPath.startsWith(":buildSrc:")) {
-                return;
-            }
-
-            if (state.getSkipped()) {
-                skippedTasks.add(taskPath);
-            }
-        }
-
-        private String path(Task task) {
-            return task.getProject().getGradle().getParent() == null ? task.getPath() : ":" + task.getProject().getRootProject().getName() + task.getPath();
-        }
-    }
-
-    public static class InProcessExecutionResult implements ExecutionResult {
-        private final List<String> plannedTasks;
-        private final Set<String> skippedTasks;
-        private final String output;
-        private final String error;
-
-        public InProcessExecutionResult(List<String> plannedTasks, Set<String> skippedTasks, String output,
-                                        String error) {
-            this.plannedTasks = plannedTasks;
-            this.skippedTasks = skippedTasks;
-            this.output = output;
-            this.error = error;
-        }
-
-        public String getOutput() {
-            return output;
-        }
-
-        public ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines) {
-            new SequentialOutputMatcher().assertOutputMatches(expectedOutput, getOutput(), ignoreExtraLines);
-            return this;
-        }
-
-        public String getError() {
-            return error;
-        }
-
-        public List<String> getExecutedTasks() {
-            return new ArrayList<String>(plannedTasks);
-        }
-
-        public ExecutionResult assertTasksExecuted(String... taskPaths) {
-            List<String> expected = Arrays.asList(taskPaths);
-            assertThat(plannedTasks, equalTo(expected));
-            return this;
-        }
-
-        public Set<String> getSkippedTasks() {
-            return new HashSet<String>(skippedTasks);
-        }
-
-        public ExecutionResult assertTasksSkipped(String... taskPaths) {
-            Set<String> expected = new HashSet<String>(Arrays.asList(taskPaths));
-            assertThat(skippedTasks, equalTo(expected));
-            return this;
-        }
-
-        public ExecutionResult assertTaskSkipped(String taskPath) {
-            assertThat(skippedTasks, hasItem(taskPath));
-            return this;
-        }
-
-        public ExecutionResult assertTasksNotSkipped(String... taskPaths) {
-            Set<String> expected = new HashSet<String>(Arrays.asList(taskPaths));
-            Set<String> notSkipped = getNotSkippedTasks();
-            assertThat(notSkipped, equalTo(expected));
-            return this;
-        }
-
-        public ExecutionResult assertTaskNotSkipped(String taskPath) {
-            assertThat(getNotSkippedTasks(), hasItem(taskPath));
-            return this;
-        }
-
-        private Set<String> getNotSkippedTasks() {
-            Set<String> notSkipped = new HashSet<String>(plannedTasks);
-            notSkipped.removeAll(skippedTasks);
-            return notSkipped;
-        }
-    }
-
-    private static class InProcessExecutionFailure extends InProcessExecutionResult implements ExecutionFailure {
-        private final GradleException failure;
-
-        public InProcessExecutionFailure(List<String> tasks, Set<String> skippedTasks, String output, String error,
-                                         GradleException failure) {
-            super(tasks, skippedTasks, output, error);
-            this.failure = failure;
-        }
-
-        public ExecutionFailure assertHasLineNumber(int lineNumber) {
-            assertThat(failure.getMessage(), containsString(String.format(" line: %d", lineNumber)));
-            return this;
-
-        }
-
-        public ExecutionFailure assertHasFileName(String filename) {
-            assertThat(failure.getMessage(), startsWith(String.format("%s", filename)));
-            return this;
-        }
-
-        public ExecutionFailure assertHasCause(String description) {
-            assertThatCause(startsWith(description));
-            return this;
-        }
-
-        public ExecutionFailure assertThatCause(final Matcher<String> matcher) {
-            List<Throwable> causes = new ArrayList<Throwable>();
-            extractCauses(failure, causes);
-            assertThat(causes, hasItem(hasMessage(matcher)));
-            return this;
-        }
-
-        private void extractCauses(Throwable failure, List<Throwable> causes) {
-            if (failure instanceof MultipleBuildFailures) {
-                MultipleBuildFailures exception = (MultipleBuildFailures) failure;
-                for (Throwable componentFailure : exception.getCauses()) {
-                    extractCauses(componentFailure, causes);
-                }
-            } else if (failure instanceof LocationAwareException) {
-                causes.addAll(((LocationAwareException) failure).getReportableCauses());
-            } else {
-                causes.add(failure.getCause());
-            }
-        }
-
-        public ExecutionFailure assertHasNoCause() {
-            if (failure instanceof LocationAwareException) {
-                LocationAwareException exception = (LocationAwareException) failure;
-                assertThat(exception.getReportableCauses(), isEmpty());
-            } else {
-                assertThat(failure.getCause(), nullValue());
-            }
-            return this;
-        }
-
-        public ExecutionFailure assertHasDescription(String context) {
-            assertThatDescription(startsWith(context));
-            return this;
-        }
-
-        public ExecutionFailure assertThatDescription(Matcher<String> matcher) {
-            assertThat(failure.getMessage(), containsLine(matcher));
-            return this;
-        }
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/JUnitTestExecutionResult.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/JUnitTestExecutionResult.groovy
deleted file mode 100644
index af376bd..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/JUnitTestExecutionResult.groovy
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures
-
-import groovy.util.slurpersupport.GPathResult
-import org.gradle.util.TestFile
-import org.hamcrest.Matcher
-
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.assertThat
-
-class JUnitTestExecutionResult implements TestExecutionResult {
-    private final TestFile buildDir
-
-    def JUnitTestExecutionResult(TestFile projectDir, String buildDirName = 'build') {
-        this.buildDir = projectDir.file(buildDirName)
-    }
-
-    boolean hasJUnitXmlResults() {
-        xmlResultsDir().list().length > 0
-    }
-
-    TestExecutionResult assertTestClassesExecuted(String... testClasses) {
-        Map<String, File> classes = findClasses()
-        assertThat(classes.keySet(), equalTo(testClasses as Set));
-        this
-    }
-
-    TestClassExecutionResult testClass(String testClass) {
-        return new JUnitTestClassExecutionResult(findTestClass(testClass), testClass)
-    }
-
-    private def findTestClass(String testClass) {
-        def classes = findClasses()
-        assertThat(classes.keySet(), hasItem(testClass))
-        def classFile = classes.get(testClass)
-        assertThat(classFile, notNullValue())
-        return new XmlSlurper().parse(classFile)
-    }
-
-    private def findClasses() {
-        xmlResultsDir().assertIsDir()
-        buildDir.file('reports/tests/index.html').assertIsFile()
-
-        Map<String, File> classes = [:]
-        buildDir.file('test-results').eachFile { File file ->
-            def matcher = (file.name =~ /TEST-(.+)\.xml/)
-            if (matcher.matches()) {
-                classes[matcher.group(1)] = file
-            }
-        }
-        return classes
-    }
-
-    private TestFile xmlResultsDir() {
-        buildDir.file('test-results')
-    }
-}
-
-class JUnitTestClassExecutionResult implements TestClassExecutionResult {
-    GPathResult testClassNode
-    String testClassName
-    boolean checked
-
-    def JUnitTestClassExecutionResult(GPathResult testClassNode, String testClassName) {
-        this.testClassNode = testClassNode
-        this.testClassName = testClassName
-    }
-
-    TestClassExecutionResult assertTestsExecuted(String... testNames) {
-        Map<String, Node> testMethods = findTests()
-        assertThat(testMethods.keySet(), equalTo(testNames as Set))
-        this
-    }
-
-    TestClassExecutionResult assertTestCount(int tests, int failures, int errors) {
-        assert testClassNode. at tests == tests
-        assert testClassNode. at failures == failures
-        assert testClassNode. at errors == errors
-        this
-    }
-
-    TestClassExecutionResult assertTestPassed(String name) {
-        Map<String, Node> testMethods = findTests()
-        assertThat(testMethods.keySet(), hasItem(name))
-        assertThat(testMethods[name].failure.size(), equalTo(0))
-        this
-    }
-
-    TestClassExecutionResult assertTestFailed(String name, Matcher<? super String>... messageMatchers) {
-        Map<String, Node> testMethods = findTests()
-        assertThat(testMethods.keySet(), hasItem(name))
-
-        def failures = testMethods[name].failure
-        assertThat("Expected ${messageMatchers.length} failures. Found: $failures", failures.size(), equalTo(messageMatchers.length))
-
-        for (int i = 0; i < messageMatchers.length; i++) {
-            assertThat(failures[i]. at message.text(), messageMatchers[i])
-        }
-        this
-    }
-
-    TestClassExecutionResult assertTestSkipped(String name) {
-        throw new UnsupportedOperationException()
-    }
-
-    TestClassExecutionResult assertTestsSkipped(String... testNames) {
-        Map<String, Node> testMethods = findIgnoredTests()
-        assertThat(testMethods.keySet(), equalTo(testNames as Set))
-        this
-    }
-
-    TestClassExecutionResult assertConfigMethodPassed(String name) {
-        throw new UnsupportedOperationException();
-    }
-
-    TestClassExecutionResult assertConfigMethodFailed(String name) {
-        throw new UnsupportedOperationException();
-    }
-
-    TestClassExecutionResult assertStdout(Matcher<? super String> matcher) {
-        def stdout = testClassNode.'system-out'[0].text();
-        assertThat(stdout, matcher)
-        this
-    }
-
-    TestClassExecutionResult assertStderr(Matcher<? super String> matcher) {
-        def stderr = testClassNode.'system-err'[0].text();
-        assertThat(stderr, matcher)
-        this
-    }
-
-    private def findTests() {
-        if (!checked) {
-            assertThat(testClassNode.name(), equalTo('testsuite'))
-            assertThat(testClassNode. at name.text(), equalTo(testClassName))
-            assertThat(testClassNode. at tests.text(), not(equalTo('')))
-            assertThat(testClassNode. at failures.text(), not(equalTo('')))
-            assertThat(testClassNode. at errors.text(), not(equalTo('')))
-            assertThat(testClassNode. at time.text(), not(equalTo('')))
-            assertThat(testClassNode. at timestamp.text(), not(equalTo('')))
-            assertThat(testClassNode. at hostname.text(), not(equalTo('')))
-            assertThat(testClassNode.properties.size(), equalTo(1))
-            testClassNode.testcase.each { node ->
-                assertThat(node. at classname.text(), equalTo(testClassName))
-                assertThat(node. at name.text(), not(equalTo('')))
-                assertThat(node. at time.text(), not(equalTo('')))
-                node.failure.each { failure ->
-                    assertThat(failure. at message.size(), equalTo(1))
-                    assertThat(failure. at type.text(), not(equalTo('')))
-                    assertThat(failure.text(), not(equalTo('')))
-                }
-            }
-            assertThat(testClassNode.'system-out'.size(), equalTo(1))
-            assertThat(testClassNode.'system-err'.size(), equalTo(1))
-            checked = true
-        }
-        Map testMethods = [:]
-        testClassNode.testcase.each { testMethods[it. at name.text()] = it }
-        return testMethods
-    }
-
-    private def findIgnoredTests() {
-        Map testMethods = [:]
-        testClassNode."ignored-testcase".each { testMethods[it. at name.text()] = it }
-        return testMethods
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenFileModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenFileModule.groovy
deleted file mode 100644
index 3ba85ac..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenFileModule.groovy
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures
-
-import groovy.xml.MarkupBuilder
-import org.gradle.util.TestFile
-import org.gradle.util.hash.HashUtil
-
-import java.text.SimpleDateFormat
-
-class MavenFileModule implements MavenModule {
-    final TestFile moduleDir
-    final String groupId
-    final String artifactId
-    final String version
-    String parentPomSection
-    String type = 'jar'
-    String packaging
-    private final List dependencies = []
-    int publishCount = 1
-    final updateFormat = new SimpleDateFormat("yyyyMMddHHmmss")
-    final timestampFormat = new SimpleDateFormat("yyyyMMdd.HHmmss")
-    private final List artifacts = []
-    private boolean uniqueSnapshots = true;
-
-    MavenFileModule(TestFile moduleDir, String groupId, String artifactId, String version) {
-        this.moduleDir = moduleDir
-        this.groupId = groupId
-        this.artifactId = artifactId
-        this.version = version
-    }
-
-    MavenFileModule dependsOn(String ... dependencyArtifactIds) {
-        for (String id : dependencyArtifactIds) {
-            dependsOn(groupId, id, '1.0')
-        }
-        return this
-    }
-
-    MavenFileModule dependsOn(String group, String artifactId, String version, String type = null) {
-        this.dependencies << [groupId: group, artifactId: artifactId, version: version, type: type]
-        return this
-    }
-
-    /**
-     * Specifies the type of the main artifact.
-     */
-    MavenFileModule hasType(String type) {
-        this.type = type
-        return this
-    }
-
-    /**
-     * Adds an additional artifact to this module.
-     * @param options Can specify any of: type or classifier
-     */
-    MavenFileModule artifact(Map<String, ?> options) {
-        artifacts << options
-        return this
-    }
-
-    MavenFileModule withNonUniqueSnapshots() {
-        uniqueSnapshots = false;
-        return this;
-    }
-
-    /**
-     * Asserts that exactly the given artifacts have been deployed, along with their checksum files
-     */
-    void assertArtifactsPublished(String... names) {
-        def artifactNames = names
-        if (uniqueSnapshots && version.endsWith('-SNAPSHOT')) {
-            def metaData = new XmlParser().parse(moduleDir.file('maven-metadata.xml'))
-            def timestamp = metaData.versioning.snapshot.timestamp[0].text().trim()
-            def build = metaData.versioning.snapshot.buildNumber[0].text().trim()
-            artifactNames = names.collect { it.replace('-SNAPSHOT', "-${timestamp}-${build}")}
-            artifactNames.add("maven-metadata.xml")
-        }
-        assert moduleDir.isDirectory()
-        Set actual = moduleDir.list() as Set
-        for (name in artifactNames) {
-            assert actual.remove(name)
-            assert actual.remove("${name}.md5" as String)
-            assert actual.remove("${name}.sha1" as String)
-        }
-        assert actual.isEmpty()
-    }
-
-    MavenPom getPom() {
-        return new MavenPom(pomFile)
-    }
-
-    TestFile getPomFile() {
-        return moduleDir.file("$artifactId-${publishArtifactVersion}.pom")
-    }
-
-    TestFile getMetaDataFile() {
-        moduleDir.file("maven-metadata.xml")
-    }
-
-    TestFile getRootMetaDataFile() {
-        moduleDir.parentFile.file("maven-metadata.xml")
-    }
-
-    TestFile getArtifactFile() {
-        return artifactFile([:])
-    }
-
-    TestFile artifactFile(Map<String, ?> options) {
-        def artifact = toArtifact(options)
-        def fileName = "$artifactId-${publishArtifactVersion}.${artifact.type}"
-        if (artifact.classifier) {
-            fileName = "$artifactId-$publishArtifactVersion-${artifact.classifier}.${artifact.type}"
-        }
-        return moduleDir.file(fileName)
-    }
-
-    MavenFileModule publishWithChangedContent() {
-        publishCount++
-        return publish()
-    }
-
-    String getPublishArtifactVersion() {
-        if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
-            return "${version.replaceFirst('-SNAPSHOT$', '')}-${timestampFormat.format(publishTimestamp)}-${publishCount}"
-        }
-        return version
-    }
-
-    Date getPublishTimestamp() {
-        return new Date(updateFormat.parse("20100101120000").time + publishCount * 1000)
-    }
-
-    MavenModule publish() {
-        moduleDir.createDir()
-        def rootMavenMetaData = getRootMetaDataFile()
-
-        updateRootMavenMetaData(rootMavenMetaData)
-        if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
-            def metaDataFile = moduleDir.file('maven-metadata.xml')
-            publish(metaDataFile) {
-                metaDataFile.text = """
-<metadata>
-  <!-- $publishCount -->
-  <groupId>$groupId</groupId>
-  <artifactId>$artifactId</artifactId>
-  <version>$version</version>
-  <versioning>
-    <snapshot>
-      <timestamp>${timestampFormat.format(publishTimestamp)}</timestamp>
-      <buildNumber>$publishCount</buildNumber>
-    </snapshot>
-    <lastUpdated>${updateFormat.format(publishTimestamp)}</lastUpdated>
-  </versioning>
-</metadata>
-"""
-            }
-        }
-
-        publish(pomFile) {
-            def pomPackaging = packaging ?: type;
-            pomFile.text = ""
-            pomFile << """
-<project xmlns="http://maven.apache.org/POM/4.0.0">
-  <modelVersion>4.0.0</modelVersion>
-  <groupId>$groupId</groupId>
-  <artifactId>$artifactId</artifactId>
-  <packaging>$pomPackaging</packaging>
-  <version>$version</version>
-  <description>Published on $publishTimestamp</description>"""
-
-            if (parentPomSection) {
-                pomFile << "\n$parentPomSection\n"
-            }
-
-            if (!dependencies.empty) {
-                pomFile << """
-  <dependencies>"""
-            }
-
-            dependencies.each { dependency ->
-                def typeAttribute = dependency['type'] == null ? "" : "<type>$dependency.type</type>"
-                pomFile << """
-    <dependency>
-      <groupId>$dependency.groupId</groupId>
-      <artifactId>$dependency.artifactId</artifactId>
-      <version>$dependency.version</version>
-      $typeAttribute
-    </dependency>"""
-            }
-
-            if (!dependencies.empty) {
-                pomFile << """
-  </dependencies>"""
-            }
-
-            pomFile << "\n</project>"
-        }
-
-        artifacts.each { artifact ->
-            publishArtifact(artifact)
-        }
-        publishArtifact([:])
-        return this
-    }
-
-    private void updateRootMavenMetaData(TestFile rootMavenMetaData) {
-        def allVersions = rootMavenMetaData.exists() ? new XmlParser().parseText(rootMavenMetaData.text).versioning.versions.version*.value().flatten() : []
-        allVersions << version;
-        publish(rootMavenMetaData) {
-            rootMavenMetaData.withWriter {writer ->
-                def builder = new MarkupBuilder(writer)
-                builder.metadata {
-                    groupId(groupId)
-                    artifactId(artifactId)
-                    version(allVersions.max())
-                    versioning {
-                        if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
-                            snapshot {
-                                timestamp(timestampFormat.format(publishTimestamp))
-                                buildNumber(publishCount)
-                                lastUpdated(updateFormat.format(publishTimestamp))
-                            }
-                        } else {
-                            versions {
-                                allVersions.each{currVersion ->
-                                    version(currVersion)
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    private File publishArtifact(Map<String, ?> artifact) {
-        def artifactFile = artifactFile(artifact)
-        publish(artifactFile) {
-            if (type != 'pom') {
-                artifactFile.text = "${artifactFile.name} : $publishCount"
-            }
-        }
-        return artifactFile
-    }
-
-    private publish(File file, Closure cl) {
-        def lastModifiedTime = file.exists() ? file.lastModified() : null
-        cl.call(file)
-        if (lastModifiedTime != null) {
-            file.setLastModified(lastModifiedTime + 2000)
-        }
-        createHashFiles(file)
-    }
-
-    private Map<String, Object> toArtifact(Map<String, ?> options) {
-        options = new HashMap<String, Object>(options)
-        def artifact = [type: options.remove('type') ?: type, classifier: options.remove('classifier') ?: null]
-        assert options.isEmpty(): "Unknown options : ${options.keySet()}"
-        return artifact
-    }
-
-    private void createHashFiles(File file) {
-        sha1File(file)
-        md5File(file)
-    }
-
-    TestFile sha1File(File file) {
-        hashFile(file, "sha1");
-    }
-
-    TestFile md5File(File file) {
-        hashFile(file, "md5")
-    }
-
-    private TestFile hashFile(TestFile file, String algorithm) {
-        def hashFile = file.parentFile.file("${file.name}.${algorithm}")
-        hashFile.text = HashUtil.createHash(file, algorithm.toUpperCase()).asHexString()
-        return hashFile
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenFileRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenFileRepository.groovy
deleted file mode 100644
index ba220be..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenFileRepository.groovy
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures
-
-import org.gradle.util.TestFile
-
-/**
- * A fixture for dealing with local Maven repositories.
- */
-class MavenFileRepository implements MavenRepository {
-    final TestFile rootDir
-
-    MavenFileRepository(TestFile rootDir) {
-        this.rootDir = rootDir
-    }
-
-    URI getUri() {
-        return rootDir.toURI()
-    }
-
-    MavenFileModule module(String groupId, String artifactId, Object version = '1.0') {
-        def artifactDir = rootDir.file("${groupId.replace('.', '/')}/$artifactId/$version")
-        return new MavenFileModule(artifactDir, groupId, artifactId, version as String)
-    }
-}
-
-
-
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenHttpModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenHttpModule.groovy
deleted file mode 100644
index 47b1def..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenHttpModule.groovy
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures
-
-import org.gradle.test.fixtures.server.http.HttpServer
-import org.gradle.util.TestFile
-
-class MavenHttpModule implements MavenModule {
-    private final HttpServer server
-    private final String modulePath
-    private final MavenFileModule backingModule
-
-    MavenHttpModule(HttpServer server, String modulePath, MavenFileModule backingModule) {
-        this.backingModule = backingModule
-        this.server = server
-        this.modulePath = modulePath
-    }
-
-    MavenHttpModule publish() {
-        backingModule.publish()
-        return this
-    }
-
-    MavenHttpModule publishWithChangedContent() {
-        backingModule.publishWithChangedContent()
-        return this
-    }
-
-    MavenHttpModule withNonUniqueSnapshots() {
-        backingModule.withNonUniqueSnapshots()
-        return this
-    }
-
-    MavenModule dependsOn(String group, String artifactId, String version) {
-        backingModule.dependsOn(group, artifactId, version)
-        return this
-    }
-
-    TestFile getPomFile() {
-        return backingModule.pomFile
-    }
-
-    TestFile getArtifactFile() {
-        return backingModule.artifactFile
-    }
-
-    TestFile getMetaDataFile() {
-        return backingModule.metaDataFile
-    }
-
-    void allowAll() {
-        server.allowGetOrHead(modulePath, backingModule.moduleDir)
-    }
-
-    void expectMetaDataGet() {
-        server.expectGet("$modulePath/$metaDataFile.name", metaDataFile)
-    }
-
-    void expectMetaDataGetMissing() {
-        server.expectGetMissing("$modulePath/$metaDataFile.name")
-    }
-
-    void expectPomGet() {
-        server.expectGet("$modulePath/$pomFile.name", pomFile)
-    }
-
-    void expectPomHead() {
-        server.expectHead("$modulePath/$pomFile.name", pomFile)
-    }
-
-    void allowPomHead() {
-        server.allowHead("$modulePath/$pomFile.name", pomFile)
-    }
-
-    void expectPomGetMissing() {
-        server.expectGetMissing("$modulePath/$missingPomName")
-    }
-
-    void expectPomSha1Get() {
-        server.expectGet("$modulePath/${pomFile.name}.sha1", backingModule.sha1File(pomFile))
-    }
-
-    void allowPomSha1Get() {
-        server.allowGetOrHead("$modulePath/${pomFile.name}.sha1", backingModule.sha1File(pomFile))
-    }
-
-    void expectPomSha1GetMissing() {
-        server.expectGetMissing("$modulePath/${missingPomName}.sha1")
-    }
-
-    private String getMissingPomName() {
-        if (backingModule.version.endsWith("-SNAPSHOT")) {
-            return "${backingModule.artifactId}-${backingModule.version}.pom"
-        } else {
-            return pomFile.name
-        }
-    }
-
-    void expectArtifactGet() {
-        server.expectGet("$modulePath/$artifactFile.name", artifactFile)
-    }
-
-    void expectArtifactHead() {
-        server.expectHead("$modulePath/$artifactFile.name", artifactFile)
-    }
-
-    void allowArtifactHead() {
-        server.allowHead("$modulePath/$artifactFile.name", artifactFile)
-    }
-
-    void expectArtifactGetMissing() {
-        server.expectGetMissing("$modulePath/$missingArtifactName")
-    }
-
-    void expectArtifactHeadMissing() {
-        server.expectHeadMissing("$modulePath/$missingArtifactName")
-    }
-
-    void expectArtifactSha1Get() {
-        server.expectGet("$modulePath/${artifactFile.name}.sha1", backingModule.sha1File(artifactFile))
-    }
-
-    void allowArtifactSha1Get() {
-        server.allowGetOrHead("$modulePath/${artifactFile.name}.sha1", backingModule.sha1File(artifactFile))
-    }
-
-    void expectArtifactSha1GetMissing() {
-        server.expectGetMissing("$modulePath/${missingArtifactName}.sha1")
-    }
-
-    private String getMissingArtifactName() {
-        if (backingModule.version.endsWith("-SNAPSHOT")) {
-            return "${backingModule.artifactId}-${backingModule.version}.jar"
-        } else {
-            return artifactFile.name
-        }
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenHttpRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenHttpRepository.groovy
deleted file mode 100644
index 367223e..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenHttpRepository.groovy
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures
-
-import org.gradle.test.fixtures.server.http.HttpServer
-
-/**
- * A fixture for dealing with remote HTTP Maven repositories.
- */
-class MavenHttpRepository {
-    private final HttpServer server
-    private final MavenFileRepository backingRepository
-    private final String contextPath
-
-    MavenHttpRepository(HttpServer server, String contextPath = "/repo", MavenFileRepository backingRepository) {
-        if (!contextPath.startsWith("/")) {
-            throw new IllegalArgumentException("Context path must start with '/'")
-        }
-        this.contextPath = contextPath
-        this.server = server
-        this.backingRepository = backingRepository
-    }
-
-    URI getUri() {
-        return new URI("http://localhost:${server.port}${contextPath}")
-    }
-
-    void expectMetaDataGet(String groupId, String artifactId) {
-        def path = "${groupId.replace('.', '/')}/$artifactId/maven-metadata.xml"
-        server.expectGet("$contextPath/$path", backingRepository.getRootDir().file(path))
-    }
-
-    void allowMetaDataGet(String groupId, String artifactId) {
-        def path = "${groupId.replace('.', '/')}/$artifactId/maven-metadata.xml"
-        server.allowGetOrHead("$contextPath/$path", backingRepository.getRootDir().file(path))
-    }
-
-    void expectMetaDataGetMissing(String groupId, String artifactId) {
-        def path = "${groupId.replace('.', '/')}/$artifactId/maven-metadata.xml"
-        server.expectGetMissing("$contextPath/$path")
-    }
-
-    void expectDirectoryListGet(String groupId, String artifactId) {
-        def path = "${groupId.replace('.', '/')}/$artifactId/"
-        server.expectGetDirectoryListing("$contextPath/$path", backingRepository.getRootDir().file(path))
-    }
-
-    MavenHttpModule module(String groupId, String artifactId) {
-        return module(groupId, artifactId, "1.0")
-    }
-
-    MavenHttpModule module(String groupId, String artifactId, Object version) {
-        def backingModule = backingRepository.module(groupId, artifactId, version)
-        return new MavenHttpModule(server, "$contextPath/${groupId.replace('.', '/')}/$artifactId/$version", backingModule)
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenModule.groovy
deleted file mode 100644
index f28f9c0..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenModule.groovy
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures
-
-import org.gradle.util.TestFile
-
-interface MavenModule {
-    /**
-     * Publishes the pom.xml plus main artifact, plus any additional artifacts for this module.
-     */
-    MavenModule publish()
-
-    /**
-     * Publishes the pom.xml plus main artifact, plus any additional artifacts for this module, with changed content to any
-     * previous publication.
-     */
-    MavenModule publishWithChangedContent()
-
-    MavenModule withNonUniqueSnapshots()
-
-    MavenModule dependsOn(String group, String artifactId, String version)
-
-    TestFile getPomFile()
-
-    TestFile getArtifactFile()
-
-    TestFile getMetaDataFile()
-}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenPom.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenPom.groovy
deleted file mode 100644
index ec85479..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenPom.groovy
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures
-
-class MavenPom {
-    String groupId
-    String artifactId
-    String version
-    String packaging
-    final Map<String, MavenScope> scopes = [:]
-
-    MavenPom(File pomFile) {
-        def pom = new XmlParser().parse(pomFile)
-
-        groupId = pom.groupId[0]?.text()
-        artifactId = pom.artifactId[0]?.text()
-        version = pom.version[0]?.text()
-        packaging = pom.packaging[0]?.text()
-
-        pom.dependencies.dependency.each { dep ->
-            def scopeElement = dep.scope
-            def scopeName = scopeElement ? scopeElement.text() : "runtime"
-            def scope = scopes[scopeName]
-            if (!scope) {
-                scope = new MavenScope()
-                scopes[scopeName] = scope
-            }
-            scope.addDependency(dep.groupId.text(), dep.artifactId.text(), dep.version.text())
-        }
-    }
-
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenRepository.groovy
deleted file mode 100644
index 095449e..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenRepository.groovy
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures
-
-/**
- * A fixture for dealing with Maven repositories.
- */
-interface MavenRepository {
-    URI getUri()
-
-    MavenModule module(String groupId, String artifactId)
-
-    MavenModule module(String groupId, String artifactId, Object version)
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenScope.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenScope.groovy
deleted file mode 100644
index e84ac2e..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenScope.groovy
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures
-
-class MavenScope {
-    final dependencies = []
-
-    void addDependency(String groupId, String artifactId, String version) {
-        dependencies << [groupId: groupId, artifactId: artifactId, version: version]
-    }
-
-    void assertDependsOnArtifacts(String... artifactIds) {
-        assert dependencies.collect { it.artifactId} as Set == artifactIds as Set
-    }
-
-    void assertDependsOn(String groupId, String artifactId, String version) {
-        def dep = [groupId: groupId, artifactId: artifactId, version: version]
-        if (!dependencies.find { it == dep }) {
-            throw new AssertionError("Could not find expected dependency $dep. Actual: $dependencies")
-        }
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MultiVersionIntegrationSpec.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MultiVersionIntegrationSpec.groovy
index f480977..93d8ba1 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MultiVersionIntegrationSpec.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MultiVersionIntegrationSpec.groovy
@@ -23,10 +23,6 @@ import org.gradle.util.VersionNumber
 abstract class MultiVersionIntegrationSpec extends AbstractIntegrationSpec {
     static String version
 
-    String getVersion() {
-        version
-    }
-
     static VersionNumber getVersionNumber() {
         VersionNumber.parse(version)
     }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/OutputScrapingExecutionFailure.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/OutputScrapingExecutionFailure.java
deleted file mode 100644
index 23f1a0e..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/OutputScrapingExecutionFailure.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures;
-
-import org.hamcrest.Matcher;
-
-import java.util.regex.Pattern;
-
-import static org.gradle.util.Matchers.containsLine;
-import static org.gradle.util.Matchers.matchesRegexp;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.not;
-import static org.hamcrest.Matchers.startsWith;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-
-public class OutputScrapingExecutionFailure extends OutputScrapingExecutionResult implements ExecutionFailure {
-    private final Pattern causePattern = Pattern.compile("(?m)\\s*> ");
-
-    public OutputScrapingExecutionFailure(String output, String error) {
-        super(output, error);
-    }
-
-    public ExecutionFailure assertHasLineNumber(int lineNumber) {
-        assertThat(getError(), containsString(String.format(" line: %d", lineNumber)));
-        return this;
-    }
-
-    public ExecutionFailure assertHasFileName(String filename) {
-        assertThat(getError(), containsLine(startsWith(filename)));
-        return this;
-    }
-
-    public ExecutionFailure assertHasCause(String description) {
-        assertThatCause(startsWith(description));
-        return this;
-    }
-
-    public ExecutionFailure assertThatCause(Matcher<String> matcher) {
-        String error = getError();
-        java.util.regex.Matcher regExpMatcher = causePattern.matcher(error);
-        int pos = 0;
-        while (pos < error.length()) {
-            if (!regExpMatcher.find(pos)) {
-                break;
-            }
-            int start = regExpMatcher.end();
-            String cause;
-            if (regExpMatcher.find(start)) {
-                cause = error.substring(start, regExpMatcher.start());
-                pos = regExpMatcher.start();
-            } else {
-                cause = error.substring(start);
-                pos = error.length();
-            }
-            if (matcher.matches(cause)) {
-                return this;
-            }
-        }
-        fail(String.format("No matching cause found in '%s'", error));
-        return this;
-    }
-
-    public ExecutionFailure assertHasNoCause() {
-        assertThat(getError(), not(matchesRegexp(causePattern)));
-        return this;
-    }
-
-    public ExecutionFailure assertHasDescription(String context) {
-        assertThatDescription(startsWith(context));
-        return this;
-    }
-
-    public ExecutionFailure assertThatDescription(Matcher<String> matcher) {
-        assertThat(getError(), containsLine(matcher));
-        return this;
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/OutputScrapingExecutionResult.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/OutputScrapingExecutionResult.java
deleted file mode 100644
index 71eed77..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/OutputScrapingExecutionResult.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures;
-
-import org.apache.commons.collections.CollectionUtils;
-import org.gradle.api.Action;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.*;
-import java.util.regex.Pattern;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.hasItem;
-import static org.junit.Assert.assertThat;
-
-public class OutputScrapingExecutionResult implements ExecutionResult {
-    private final String output;
-    private final String error;
-
-    //for example: ':a SKIPPED' or ':foo:bar:baz UP-TO-DATE' but not ':a'
-    private final Pattern skippedTaskPattern = Pattern.compile("(:\\S+?(:\\S+?)*)\\s+((SKIPPED)|(UP-TO-DATE))");
-
-    //for example: ':hey' or ':a SKIPPED' or ':foo:bar:baz UP-TO-DATE' but not ':a FOO'
-    private final Pattern taskPattern = Pattern.compile("(:\\S+?(:\\S+?)*)((\\s+SKIPPED)|(\\s+UP-TO-DATE)|(\\s*))");
-
-    public OutputScrapingExecutionResult(String output, String error) {
-        this.output = output;
-        this.error = error;
-    }
-
-    public String getOutput() {
-        return output;
-    }
-
-    public ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines) {
-        new SequentialOutputMatcher().assertOutputMatches(expectedOutput, getOutput(), ignoreExtraLines);
-        return this;
-    }
-
-    public String getError() {
-        return error;
-    }
-
-    public List<String> getExecutedTasks() {
-        return grepTasks(taskPattern);
-    }
-
-    public ExecutionResult assertTasksExecuted(String... taskPaths) {
-        List<String> expectedTasks = Arrays.asList(taskPaths);
-        assertThat(String.format("Expected tasks %s not found in process output:%n%s", expectedTasks, getOutput()), getExecutedTasks(), equalTo(expectedTasks));
-        return this;
-    }
-
-    public Set<String> getSkippedTasks() {
-        return new HashSet<String>(grepTasks(skippedTaskPattern));
-    }
-
-    public ExecutionResult assertTasksSkipped(String... taskPaths) {
-        Set<String> expectedTasks = new HashSet<String>(Arrays.asList(taskPaths));
-        assertThat(String.format("Expected skipped tasks %s not found in process output:%n%s", expectedTasks, getOutput()), getSkippedTasks(), equalTo(expectedTasks));
-        return this;
-    }
-
-    public ExecutionResult assertTaskSkipped(String taskPath) {
-        Set<String> tasks = new HashSet<String>(getSkippedTasks());
-        assertThat(String.format("Expected skipped task %s not found in process output:%n%s", taskPath, getOutput()), tasks, hasItem(taskPath));
-        return this;
-    }
-
-    public ExecutionResult assertTasksNotSkipped(String... taskPaths) {
-        Set<String> tasks = new HashSet<String>(getNotSkippedTasks());
-        Set<String> expectedTasks = new HashSet<String>(Arrays.asList(taskPaths));
-        assertThat(String.format("Expected executed tasks %s not found in process output:%n%s", expectedTasks, getOutput()), tasks, equalTo(expectedTasks));
-        return this;
-    }
-
-    private Collection<String> getNotSkippedTasks() {
-        List all = getExecutedTasks();
-        Set skipped = getSkippedTasks();
-        return CollectionUtils.subtract(all, skipped);
-    }
-
-    public ExecutionResult assertTaskNotSkipped(String taskPath) {
-        Set<String> tasks = new HashSet<String>(getNotSkippedTasks());
-        assertThat(String.format("Expected executed task %s not found in process output:%n%s", taskPath, getOutput()), tasks, hasItem(taskPath));
-        return this;
-    }
-
-    private List<String> grepTasks(final Pattern pattern) {
-        final LinkedList<String> tasks = new LinkedList<String>();
-
-        eachLine(new Action<String>() {
-            public void execute(String s) {
-                java.util.regex.Matcher matcher = pattern.matcher(s);
-                if (matcher.matches()) {
-                    String taskName = matcher.group(1);
-                    if (!taskName.startsWith(":buildSrc:")) {
-                        //for INFO/DEBUG level the task may appear twice - once for the execution, once for the UP-TO-DATE
-                        //so I'm not adding the task to the list if it is the same as previously added task.
-                        if (tasks.size() == 0 || !tasks.getLast().equals(taskName)) {
-                            tasks.add(taskName);
-                        }
-                    }
-                }
-            }
-        });
-
-        return tasks;
-    }
-
-    private void eachLine(Action<String> action) {
-        BufferedReader reader = new BufferedReader(new StringReader(getOutput()));
-        String line;
-        try {
-            while ((line = reader.readLine()) != null) {
-                action.execute(line);
-            }
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/OutputScrapingGradleHandle.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/OutputScrapingGradleHandle.java
deleted file mode 100644
index 6e60d2b..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/OutputScrapingGradleHandle.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures;
-
-abstract public class OutputScrapingGradleHandle implements GradleHandle {
-
-    protected ExecutionResult toExecutionResult(String output, String error) {
-        return new OutputScrapingExecutionResult(output, error);
-    }
-
-    protected ExecutionResult toExecutionFailure(String output, String error) {
-        return new OutputScrapingExecutionFailure(output, error);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ParallelForkingGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ParallelForkingGradleExecuter.java
deleted file mode 100644
index 06d213d..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ParallelForkingGradleExecuter.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures;
-
-import org.gradle.internal.Factory;
-import org.gradle.process.internal.ExecHandleBuilder;
-import org.gradle.util.TestFile;
-
-import java.util.ArrayList;
-import java.util.List;
-
-class ParallelForkingGradleExecuter extends ForkingGradleExecuter {
-    public ParallelForkingGradleExecuter(TestFile gradleHomeDir) {
-        super(gradleHomeDir);
-    }
-
-    @Override
-    protected List<String> getAllArgs() {
-        List<String> args = new ArrayList<String>();
-        args.addAll(super.getAllArgs());
-        args.add("--parallel-threads=4");
-        return args;
-    }
-
-    @Override
-    protected ForkingGradleHandle createGradleHandle(String encoding, Factory<ExecHandleBuilder> execHandleFactory) {
-        return new ParallelForkingGradleHandle(encoding, execHandleFactory);
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ParallelForkingGradleHandle.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ParallelForkingGradleHandle.java
deleted file mode 100644
index 7085d38..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ParallelForkingGradleHandle.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures;
-
-import org.gradle.internal.Factory;
-import org.gradle.process.internal.AbstractExecHandleBuilder;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertThat;
-
-public class ParallelForkingGradleHandle extends ForkingGradleHandle {
-
-    public ParallelForkingGradleHandle(String outputEncoding, Factory<? extends AbstractExecHandleBuilder> execHandleFactory) {
-        super(outputEncoding, execHandleFactory);
-    }
-
-    @Override
-    protected ExecutionResult toExecutionResult(String output, String error) {
-        return new ParallelExecutionResult(output, error);
-    }
-
-    @Override
-    protected ExecutionResult toExecutionFailure(String output, String error) {
-        return new ParallelExecutionResult(output, error);
-    }
-
-    /**
-     * Need a different output comparator for parallel execution.
-     */
-    private static class ParallelExecutionResult extends OutputScrapingExecutionFailure {
-        public ParallelExecutionResult(String output, String error) {
-            super(output, error);
-        }
-
-        @Override
-        public ExecutionResult assertTasksExecuted(String... taskPaths) {
-            Set<String> expectedTasks = new HashSet<String>(Arrays.asList(taskPaths));
-            assertThat(String.format("Expected tasks %s not found in process output:%n%s", expectedTasks, getOutput()), new HashSet<String>(getExecutedTasks()), equalTo(expectedTasks));
-            return this;
-        }
-
-        @Override
-        public String getOutput() {
-            String output = super.getOutput();
-            String parallelWarningPrefix = "Parallel project execution is an \"incubating\" feature";
-            if (output.startsWith(parallelWarningPrefix)) {
-                return output.replaceFirst(String.format("(?m)%s.+$\n", parallelWarningPrefix), "");
-            }
-            return output;
-        }
-
-        @Override
-        public ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines) {
-            new ParallelOutputMatcher().assertOutputMatches(expectedOutput, getOutput(), ignoreExtraLines);
-            return this;
-        }
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ParallelOutputMatcher.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ParallelOutputMatcher.groovy
deleted file mode 100644
index d692e5b..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ParallelOutputMatcher.groovy
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package org.gradle.integtests.fixtures
-
-import org.junit.Assert
-import org.gradle.internal.SystemProperties
-
-/**
- * Checks that all lines contained in the expected output are present in the actual output, in any order.
- */
-class ParallelOutputMatcher extends SequentialOutputMatcher {
-    private static final String NL = SystemProperties.lineSeparator
-
-    protected void assertOutputLinesMatch(List<String> expectedLines, List<String> actualLines, boolean ignoreExtraLines, String actual) {
-        List<String> unmatchedLines = new ArrayList<String>(actualLines)
-        expectedLines.removeAll('')
-        unmatchedLines.removeAll('')
-
-        expectedLines.each { expectedLine ->
-            def matchedLine = unmatchedLines.find { actualLine ->
-                compare(expectedLine, actualLine)
-            }
-            if (matchedLine) {
-                unmatchedLines.remove(matchedLine)
-            } else {
-                Assert.fail("Line missing from output.${NL}${expectedLine}${NL}---${NL}Actual output:${NL}$actual${NL}---")
-            }
-        }
-
-        if (!(ignoreExtraLines || unmatchedLines.empty)) {
-            def unmatched = unmatchedLines.join(NL)
-            Assert.fail("Extra lines in output.${NL}${unmatched}${NL}---${NL}Actual output:${NL}$actual${NL}---")
-        }
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/PreviousGradleVersionExecuter.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/PreviousGradleVersionExecuter.groovy
deleted file mode 100644
index 7a57974..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/PreviousGradleVersionExecuter.groovy
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures
-
-import org.gradle.CacheUsage
-import org.gradle.api.Action
-import org.gradle.cache.PersistentCache
-import org.gradle.cache.internal.CacheFactory
-import org.gradle.cache.internal.DefaultCacheFactory
-import org.gradle.cache.internal.DefaultFileLockManager
-import org.gradle.cache.internal.DefaultProcessMetaDataProvider
-import org.gradle.cache.internal.FileLockManager.LockMode
-import org.gradle.internal.jvm.Jvm
-import org.gradle.internal.nativeplatform.ProcessEnvironment
-import org.gradle.internal.nativeplatform.services.NativeServices
-import org.gradle.internal.os.OperatingSystem
-import org.gradle.util.DistributionLocator
-import org.gradle.util.GradleVersion
-import org.gradle.util.TestFile
-
-class PreviousGradleVersionExecuter extends AbstractDelegatingGradleExecuter implements BasicGradleDistribution {
-    private static final CACHE_FACTORY = createCacheFactory()
-
-    private static CacheFactory createCacheFactory() {
-        return new DefaultCacheFactory(
-                new DefaultFileLockManager(
-                        new DefaultProcessMetaDataProvider(
-                                NativeServices.getInstance().get(ProcessEnvironment)),
-                        20 * 60 * 1000 // allow up to 20 minutes to download a distribution
-                )).create()
-    }
-
-    private final GradleDistribution dist
-    final GradleVersion version
-    private final TestFile versionDir
-    private final TestFile zipFile
-    private final TestFile homeDir
-    private PersistentCache cache
-
-    PreviousGradleVersionExecuter(GradleDistribution dist, String version) {
-        this.dist = dist
-        this.version = GradleVersion.version(version)
-        versionDir = dist.previousVersionsDir.file(version)
-        zipFile = versionDir.file("gradle-$version-bin.zip")
-        homeDir = versionDir.file("gradle-$version")
-    }
-
-    def String toString() {
-        version.toString()
-    }
-
-    String getVersion() {
-        return version.version
-    }
-
-    boolean worksWith(Jvm jvm) {
-        // Milestone 4 was broken on the IBM jvm
-        if (jvm.ibmJvm && version == GradleVersion.version('1.0-milestone-4')) {
-            return false
-        }
-        // 0.9-rc-1 was broken for Java 5
-        if (version == GradleVersion.version('0.9-rc-1')) {
-            return jvm.javaVersion.isJava6Compatible()
-        }
-
-        return jvm.javaVersion.isJava5Compatible()
-    }
-
-    boolean worksWith(OperatingSystem os) {
-        // 1.0-milestone-5 was broken where jna was not available
-        if (version == GradleVersion.version("1.0-milestone-5")) {
-            return os.windows || os.macOsX || os.linux
-        }
-        return true
-    }
-
-    boolean isDaemonSupported() {
-        // Milestone 7 was broken on the IBM jvm
-        if (Jvm.current().ibmJvm && version == GradleVersion.version('1.0-milestone-7')) {
-            return false
-        }
-
-        if (OperatingSystem.current().isWindows()) {
-            // On windows, daemon is ok for anything > 1.0-milestone-3
-            return version > GradleVersion.version('1.0-milestone-3')
-        } else {
-            // Daemon is ok for anything >= 0.9
-            return version >= GradleVersion.version('0.9')
-        }
-    }
-
-    boolean isDaemonIdleTimeoutConfigurable() {
-        return version > GradleVersion.version('1.0-milestone-6')
-    }
-
-    boolean isOpenApiSupported() {
-        return version >= GradleVersion.version('0.9-rc-1')
-    }
-
-    boolean isToolingApiSupported() {
-        return version >= GradleVersion.version('1.0-milestone-3')
-    }
-
-    boolean isMultiProcessSafeCache() {
-        return version >= GradleVersion.version('1.0-milestone-5')
-    }
-
-    boolean wrapperCanExecute(String version) {
-        if (version == '0.8' || this.version == GradleVersion.version('0.8')) {
-            // There was a breaking change after 0.8
-            return false
-        }
-        if (this.version == GradleVersion.version('0.9.1')) {
-            // 0.9.1 couldn't handle anything with a timestamp whose timezone was behind GMT
-            return version.matches('.*+\\d{4}')
-        }
-        if (this.version >= GradleVersion.version('0.9.2') && this.version <= GradleVersion.version('1.0-milestone-2')) {
-            // These versions couldn't handle milestone patches
-            if (version.matches('1.0-milestone-\\d+[a-z]-.+')) {
-                return false
-            }
-        }
-        return true
-    }
-
-    @Override
-    protected GradleExecuter configureExecuter() {
-        ForkingGradleExecuter executer = new ForkingGradleExecuter(gradleHomeDir)
-        executer.inDirectory(dist.testDir)
-        copyTo(executer)
-        return executer
-    }
-
-    GradleExecuter executer() {
-        this
-    }
-
-    TestFile getBinDistribution() {
-        download()
-        return zipFile
-    }
-
-    private URL getBinDistributionUrl() {
-        return new DistributionLocator().getDistributionFor(version).toURL()
-    }
-
-    def TestFile getGradleHomeDir() {
-        download()
-        return homeDir
-    }
-
-    private void download() {
-        if (cache == null) {
-            def downloadAction = { cache ->
-                URL url = binDistributionUrl
-                System.out.println("downloading $url");
-                zipFile.copyFrom(url)
-                zipFile.usingNativeTools().unzipTo(versionDir)
-            }
-            cache = CACHE_FACTORY.open(versionDir, version.toString(), CacheUsage.ON, null, [:], LockMode.Shared, downloadAction as Action)
-        }
-        zipFile.assertIsFile()
-        homeDir.assertIsDir()
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ProgressLoggingFixture.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ProgressLoggingFixture.groovy
deleted file mode 100644
index c267d5c..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ProgressLoggingFixture.groovy
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures
-
-import org.gradle.util.TestFile
-import org.junit.rules.MethodRule
-import org.junit.runners.model.FrameworkMethod
-import org.junit.runners.model.Statement
-
-class ProgressLoggingFixture implements MethodRule {
-
-    private TestFile loggingOutputFile = null
-
-    boolean downloadProgressLogged(String url) {
-        return progressLogged("Download", url)
-    }
-
-    boolean uploadProgressLogged(String url) {
-        return progressLogged("Upload", url)
-    }
-
-    private boolean progressLogged(String operation, String url) {
-        def lines = loggingOutputFile.exists() ? loggingOutputFile.text.readLines() : []
-        def startIndex = lines.indexOf("[START " + operation + " " + url + "]")
-        if (startIndex == -1) {
-            return false
-        }
-        lines = lines[startIndex..<lines.size()]
-        lines = lines[0..lines.indexOf("[END " + operation + " " + url + "]")]
-        lines.size() >= 2
-    }
-
-    Statement apply(Statement base, FrameworkMethod method, Object target) {
-        TestFile initFile
-        GradleDistributionExecuter executer = RuleHelper.getField(target, GradleDistributionExecuter)
-        GradleDistribution distribution = RuleHelper.getField(target, GradleDistribution)
-        TestFile temporaryFolder = distribution.temporaryFolder.dir
-        loggingOutputFile = temporaryFolder.file("loggingoutput.log")
-        initFile = temporaryFolder.file("progress-logging-init.gradle")
-        initFile.text = """import org.gradle.logging.internal.*
-                           File outputFile = file("${loggingOutputFile.toURI()}")
-                           OutputEventListener outputEventListener = new OutputEventListener() {
-                                void onOutput(OutputEvent event) {
-                                    if (event instanceof ProgressStartEvent) {
-                                        outputFile << "[START \$event.description]\\n"
-                                    } else if (event instanceof ProgressEvent) {
-                                        outputFile << "[\$event.status]\\n"
-                                    } else if (event instanceof ProgressCompleteEvent) {
-                                        outputFile << "[END \$event.description]\\n"
-                                    }
-                                }
-                           }
-                           def loggingOutputInternal = gradle.services.get(LoggingOutputInternal)
-                           loggingOutputInternal.addOutputEventListener(outputEventListener)
-                           buildFinished{
-                                loggingOutputInternal.removeOutputEventListener(outputEventListener)
-                           }"""
-        executer.beforeExecute {
-            usingInitScript(initFile)
-        }
-
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                base.evaluate();
-            }
-        };
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RedirectMavenCentral.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RedirectMavenCentral.groovy
deleted file mode 100644
index 3469e35..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RedirectMavenCentral.groovy
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.fixtures
-
-import org.gradle.api.Action
-import org.gradle.internal.os.OperatingSystem
-import org.gradle.util.TemporaryFolder
-
-class RedirectMavenCentral implements Action<GradleExecuter> {
-    private final TemporaryFolder temporaryFolder
-
-    RedirectMavenCentral(TemporaryFolder temporaryFolder) {
-        this.temporaryFolder = temporaryFolder
-    }
-
-    void execute(GradleExecuter executer) {
-        if (OperatingSystem.current().isWindows()) {
-            return
-        }
-
-        def file = temporaryFolder.createFile("redirect-maven-central-init.gradle")
-        file.text = """
-allprojects {
-    repositories.withType(org.gradle.api.artifacts.repositories.MavenArtifactRepository) {
-        if (url == new URI('http://repo1.maven.org/maven2/')) {
-            url = "http://repo.gradle.org/gradle/repo1"
-        }
-    }
-}
-"""
-        executer.withArgument("-I$file.absolutePath")
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ReleasedVersions.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ReleasedVersions.java
deleted file mode 100644
index ecc3741..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ReleasedVersions.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures;
-
-import org.gradle.api.Transformer;
-import org.gradle.integtests.fixtures.versions.VersionsInfo;
-import org.gradle.util.CollectionUtils;
-
-import java.util.List;
-
-import static org.gradle.util.GradleVersion.version;
-
-/**
- * by Szczepan Faber, created at: 1/12/12
- */
-public class ReleasedVersions {
-
-    private final static List<String> VERSIONS = new VersionsInfo().getVersions();
-
-    private final GradleDistribution current;
-
-    public ReleasedVersions(GradleDistribution current) {
-        this.current = current;
-    }
-
-    /**
-     * @return last released version. Never returns the RC.
-     */
-    public BasicGradleDistribution getLast() {
-        for (String v : VERSIONS) {
-            if (!version(v).isSnapshot()) {
-                return current.previousVersion(v);
-            }
-        }
-        throw new RuntimeException("Unable to get the last version");
-    }
-
-    public List<BasicGradleDistribution> getAll() {
-        return CollectionUtils.collect(VERSIONS, new Transformer<BasicGradleDistribution, String>() {
-            public BasicGradleDistribution transform(String original) {
-                return current.previousVersion(original);
-            }
-        });
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RuleHelper.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RuleHelper.java
deleted file mode 100644
index 2e51654..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RuleHelper.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.List;
-
-public class RuleHelper {
-    public static <T> T getField(Object target, Class<T> type) {
-        T value = findField(target, type);
-        if (value != null) {
-            return value;
-        }
-        throw new RuntimeException(String.format("Cannot find a field of type %s for test class %s.",
-                type.getSimpleName(), target.getClass().getSimpleName()));
-    }
-
-    public static <T> T findField(Object target, Class<T> type) {
-        List<T> matches = new ArrayList<T>();
-        for (Class<?> cl = target.getClass(); cl != Object.class; cl = cl.getSuperclass()) {
-            for (Field field : cl.getDeclaredFields()) {
-                if (type.isAssignableFrom(field.getType())) {
-                    field.setAccessible(true);
-                    try {
-                        matches.add(type.cast(field.get(target)));
-                    } catch (IllegalAccessException e) {
-                        throw new RuntimeException(e);
-                    }
-                }
-            }
-        }
-        if (matches.isEmpty()) {
-            return null;
-        }
-        if (matches.size() > 1) {
-            throw new RuntimeException(String.format("Multiple %s fields found for test class %s.",
-                    type.getSimpleName(), target.getClass().getSimpleName()));
-        }
-        return matches.get(0);
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/Sample.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/Sample.java
index f806d46..06e1a57 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/Sample.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/Sample.java
@@ -16,7 +16,10 @@
 
 package org.gradle.integtests.fixtures;
 
-import org.gradle.util.TestFile;
+import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext;
+import org.gradle.test.fixtures.file.TestDirectoryProvider;
+import org.gradle.test.fixtures.file.TestDirectoryProviderFinder;
+import org.gradle.test.fixtures.file.TestFile;
 import org.junit.rules.MethodRule;
 import org.junit.runners.model.FrameworkMethod;
 import org.junit.runners.model.Statement;
@@ -31,7 +34,7 @@ import org.slf4j.LoggerFactory;
 public class Sample implements MethodRule {
     private final Logger logger = LoggerFactory.getLogger(Sample.class);
     private final String defaultSampleName;
-    private GradleDistribution dist;
+
     private TestFile sampleDir;
 
     public Sample(String defaultSampleName) {
@@ -43,15 +46,16 @@ public class Sample implements MethodRule {
     }
 
     public Statement apply(final Statement base, FrameworkMethod method, Object target) {
-        dist = RuleHelper.getField(target, GradleDistribution.class);
+        final TestDirectoryProvider testDirectoryProvider = new TestDirectoryProviderFinder().findFor(target);
+
         final String sampleName = getSampleName(method);
-        sampleDir = sampleName == null ? null : dist.getTestDir().file(sampleName);
+        sampleDir = sampleName == null ? null : testDirectoryProvider.getTestDirectory().file(sampleName);
 
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
                 if (sampleName != null) {
-                    TestFile srcDir = dist.getSamplesDir().file(sampleName).assertIsDir();
+                    TestFile srcDir = new IntegrationTestBuildContext().getSamplesDir().file(sampleName).assertIsDir();
                     logger.debug("Copying sample '{}' to test directory.", sampleName);
                     srcDir.copyTo(sampleDir);
                 } else {
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/SequentialOutputMatcher.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/SequentialOutputMatcher.groovy
deleted file mode 100644
index 8a7eb23..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/SequentialOutputMatcher.groovy
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package org.gradle.integtests.fixtures
-
-import org.gradle.internal.SystemProperties
-import org.junit.Assert
-import org.gradle.util.TextUtil
-
-/**
- * Check that the actual output lines match the expected output lines in content and order.
- */
-class SequentialOutputMatcher {
-    private static final String NL = SystemProperties.lineSeparator
-
-    public void assertOutputMatches(String expected, String actual, boolean ignoreExtraLines) {
-        List actualLines = normaliseOutput(actual.readLines())
-        List expectedLines = expected.readLines()
-        assertOutputLinesMatch(expectedLines, actualLines, ignoreExtraLines, actual)
-    }
-
-    protected void assertOutputLinesMatch(List<String> expectedLines, List<String> actualLines, boolean ignoreExtraLines, String actual) {
-        int pos = 0
-        for (; pos < actualLines.size() && pos < expectedLines.size(); pos++) {
-            String expectedLine = expectedLines[pos]
-            String actualLine = actualLines[pos]
-            boolean matches = compare(expectedLine, actualLine)
-            if (!matches) {
-                if (expectedLine.contains(actualLine)) {
-                    Assert.fail("Missing text at line ${pos + 1}.${NL}Expected: ${expectedLine}${NL}Actual: ${actualLine}${NL}---${NL}Actual output:${NL}$actual${NL}---")
-                }
-                if (actualLine.contains(expectedLine)) {
-                    Assert.fail("Extra text at line ${pos + 1}.${NL}Expected: ${expectedLine}${NL}Actual: ${actualLine}${NL}---${NL}Actual output:${NL}$actual${NL}---")
-                }
-                Assert.fail("Unexpected value at line ${pos + 1}.${NL}Expected: ${expectedLine}${NL}Actual: ${actualLine}${NL}---${NL}Actual output:${NL}$actual${NL}---")
-            }
-        }
-        if (pos == actualLines.size() && pos < expectedLines.size()) {
-            Assert.fail("Lines missing from actual result, starting at line ${pos + 1}.${NL}Expected: ${expectedLines[pos]}${NL}Actual output:${NL}$actual${NL}---")
-        }
-        if (!ignoreExtraLines && pos < actualLines.size() && pos == expectedLines.size()) {
-            Assert.fail("Extra lines in actual result, starting at line ${pos + 1}.${NL}Actual: ${actualLines[pos]}${NL}Actual output:${NL}$actual${NL}---")
-        }
-    }
-
-    private List<String> normaliseOutput(List<String> lines) {
-        if (lines.empty) {
-            return lines;
-        }
-        List<String> result = new ArrayList<String>()
-        for (String line : lines) {
-            if (line.matches('Download .+')) {
-                // ignore
-            } else {
-                result << line
-            }
-        }
-        return result
-    }
-
-    protected boolean compare(String expected, String actual) {
-        if (actual == expected) {
-            return true
-        }
-
-        if (expected == 'Total time: 1 secs') {
-            return actual.matches('Total time: .+ secs')
-        }
-
-        // Normalise default object toString() values
-        actual = actual.replaceAll('(\\w+(\\.\\w+)*)@\\p{XDigit}+', '$1 at 12345')
-        // Normalise file separators
-        actual = TextUtil.normaliseFileSeparators(actual)
-
-        return actual == expected
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestExecutionResult.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestExecutionResult.java
deleted file mode 100755
index 74e89c3..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestExecutionResult.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures;
-
-public interface TestExecutionResult {
-    /**
-     * Asserts that the given test classes (and only the given test classes) were executed.
-     */
-    TestExecutionResult assertTestClassesExecuted(String... testClasses);
-
-    /**
-     * Returns the result for the given test class.
-     */
-    TestClassExecutionResult testClass(String testClass);
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestNGExecutionResult.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestNGExecutionResult.groovy
index 49a1065..0772dca 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestNGExecutionResult.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestNGExecutionResult.groovy
@@ -17,7 +17,7 @@
 package org.gradle.integtests.fixtures
 
 import groovy.util.slurpersupport.GPathResult
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.hamcrest.Matcher
 
 /**
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestResources.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestResources.java
index ff3e678..ddb0bc4 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestResources.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestResources.java
@@ -16,9 +16,10 @@
 
 package org.gradle.integtests.fixtures;
 
+import org.gradle.test.fixtures.file.TestDirectoryProvider;
+import org.gradle.test.fixtures.file.TestDirectoryProviderFinder;
+import org.gradle.test.fixtures.file.TestFile;
 import org.gradle.util.Resources;
-import org.gradle.util.TemporaryFolder;
-import org.gradle.util.TestFile;
 import org.junit.rules.MethodRule;
 import org.junit.runners.model.FrameworkMethod;
 import org.junit.runners.model.Statement;
@@ -36,7 +37,7 @@ import java.util.Collection;
  */
 public class TestResources implements MethodRule {
     private final Logger logger = LoggerFactory.getLogger(TestResources.class);
-    private TemporaryFolder temporaryFolder;
+    private TestDirectoryProvider testWorkDirProvider;
     private final Collection<String> extraResources;
     private final Resources resources = new Resources();
 
@@ -50,12 +51,12 @@ public class TestResources implements MethodRule {
     }
 
     public TestFile getDir() {
-        return temporaryFolder.getDir();
+        return testWorkDirProvider.getTestDirectory();
     }
 
     public Statement apply(Statement base, final FrameworkMethod method, Object target) {
         final Statement statement = resources.apply(base, method, target);
-        temporaryFolder = findTempDir(target);
+        testWorkDirProvider = new TestDirectoryProviderFinder().findFor(target);
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
@@ -70,20 +71,6 @@ public class TestResources implements MethodRule {
         };
     }
 
-    private TemporaryFolder findTempDir(Object target) {
-        GradleDistribution dist = RuleHelper.findField(target, GradleDistribution.class);
-        if (dist != null) {
-            return dist.getTemporaryFolder();
-        }
-        TemporaryFolder folder = RuleHelper.findField(target, TemporaryFolder.class);
-        if (folder != null) {
-            return folder;
-        }
-        throw new RuntimeException(String.format(
-                "Could not find a GradleDistribution or TemporaryFolder field for test class %s.",
-                target.getClass().getSimpleName()));
-    }
-
     /**
      * Copies the given resource to the test directory.
      */
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UnexpectedBuildFailure.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UnexpectedBuildFailure.java
deleted file mode 100644
index 3593215..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UnexpectedBuildFailure.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures;
-
-public class UnexpectedBuildFailure extends RuntimeException {
-    public UnexpectedBuildFailure(String message) {
-        super(message);
-    }
-
-    public UnexpectedBuildFailure(Exception e) {
-        super(e);
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UserGuideSamplesRunner.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UserGuideSamplesRunner.groovy
index 3373825..3bff9ef 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UserGuideSamplesRunner.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UserGuideSamplesRunner.groovy
@@ -16,12 +16,14 @@
 package org.gradle.integtests.fixtures
 
 import com.google.common.collect.ArrayListMultimap
-import com.google.common.collect.ListMultimap
 import groovy.io.PlatformLineWriter
-import junit.framework.AssertionFailedError
 import org.apache.tools.ant.taskdefs.Delete
+import org.gradle.integtests.fixtures.executer.*
 import org.gradle.internal.SystemProperties
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.AntUtil
+import org.gradle.util.TextUtil
 import org.junit.Assert
 import org.junit.runner.Description
 import org.junit.runner.Runner
@@ -29,26 +31,28 @@ import org.junit.runner.notification.Failure
 import org.junit.runner.notification.RunNotifier
 
 import java.util.regex.Pattern
-import org.gradle.util.TextUtil
 
 class UserGuideSamplesRunner extends Runner {
     private static final String NL = SystemProperties.lineSeparator
 
-    Class<?> testClass
-    Description description
-    Map<Description, SampleRun> samples
-    GradleDistribution dist = new GradleDistribution()
-    GradleDistributionExecuter executer = new GradleDistributionExecuter(dist)
-    Pattern dirFilter = null
-    List excludes
-
-    def UserGuideSamplesRunner(Class<?> testClass) {
+    private Class<?> testClass
+    private Description description
+    private Map<Description, SampleRun> samples
+    private TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
+    private GradleDistribution dist = new UnderDevelopmentGradleDistribution()
+    private IntegrationTestBuildContext buildContext = new IntegrationTestBuildContext()
+    private GradleExecuter executer = new GradleContextualExecuter(dist, temporaryFolder)
+    private Pattern dirFilter
+    private List excludes
+    private TestFile baseExecutionDir = temporaryFolder.testDirectory
+
+    UserGuideSamplesRunner(Class<?> testClass) {
         this.testClass = testClass
         this.description = Description.createSuiteDescription(testClass)
-        this.dirFilter = getDirFilterPattern()
-        this.excludes = getExcludes()
+        this.dirFilter = initDirFilterPattern()
+        this.excludes = initExcludes()
         samples = new LinkedHashMap()
-        for (sample in getScriptsForSamples(dist.userGuideInfoDir)) {
+        for (sample in getScriptsForSamples(buildContext.userGuideInfoDir)) {
             if (shouldInclude(sample)) {
                 Description childDescription = Description.createTestDescription(testClass, sample.id)
                 description.addChild(childDescription)
@@ -58,14 +62,17 @@ class UserGuideSamplesRunner extends Runner {
                 sample.runs.each { println "    args: $it.args expect: $it.outputFile" }
             }
         }
+
+        // have to copy everything upfront because build scripts of some samples refer to files of other samples
+        buildContext.samplesDir.copyTo(baseExecutionDir)
     }
 
-    private static Pattern getDirFilterPattern() {
+    private Pattern initDirFilterPattern() {
         String filter = System.properties["org.gradle.userguide.samples.filter"]
         filter ? Pattern.compile(filter) : null
     }
 
-    def getExcludes() {
+    private List initExcludes() {
         List excludes = []
         String excludesString = System.properties["org.gradle.userguide.samples.exclude"] ?: "";
         excludesString.split(',').each {
@@ -75,7 +82,7 @@ class UserGuideSamplesRunner extends Runner {
     }
 
     Description getDescription() {
-        return description
+        description
     }
 
     private boolean shouldInclude(SampleRun run) {
@@ -88,7 +95,7 @@ class UserGuideSamplesRunner extends Runner {
     void run(RunNotifier notifier) {
         for (childDescription in description.children) {
             notifier.fireTestStarted(childDescription)
-            SampleRun sampleRun = samples.get(childDescription)
+            def sampleRun = samples.get(childDescription)
             try {
                 cleanup(sampleRun)
                 for (run in sampleRun.runs) {
@@ -101,9 +108,9 @@ class UserGuideSamplesRunner extends Runner {
         }
     }
 
-    private def cleanup(SampleRun run) {
-        // Clean up previous runs
-        File rootProjectDir = dist.samplesDir.file(run.subDir)
+    private void cleanup(SampleRun run) {
+        // Clean up previous runs in the same subdir
+        File rootProjectDir = temporaryFolder.testDirectory.file(run.subDir)
         if (rootProjectDir.exists()) {
             def delete = new Delete()
             delete.dir = rootProjectDir
@@ -112,20 +119,19 @@ class UserGuideSamplesRunner extends Runner {
         }
     }
 
-    private def runSample(GradleRun run) {
+    private void runSample(GradleRun run) {
         try {
-            println("Test Id: $run.id, dir: $run.subDir, args: $run.args")
-            File rootProjectDir = dist.samplesDir.file(run.subDir)
+            println("Test Id: $run.id, dir: $run.subDir, execution dir: $run.executionDir args: $run.args")
 
-            executer.setAllowExtraLogging(false).inDirectory(rootProjectDir).withArguments(run.args as String[]).withEnvironmentVars(run.envs)
+            executer.setAllowExtraLogging(false).inDirectory(run.executionDir).withArguments(run.args as String[]).withEnvironmentVars(run.envs)
 
-            ExecutionResult result = run.expectFailure ? executer.runWithFailure() : executer.run()
+            def result = run.expectFailure ? executer.runWithFailure() : executer.run()
             if (run.outputFile) {
-                String expectedResult = replaceWithPlatformNewLines(dist.userGuideOutputDir.file(run.outputFile).text)
+                def expectedResult = replaceWithPlatformNewLines(buildContext.userGuideOutputDir.file(run.outputFile).text)
                 expectedResult = replaceWithRealSamplesDir(expectedResult)
                 try {
                     result.assertOutputEquals(expectedResult, run.ignoreExtraLines)
-                } catch (AssertionFailedError e) {
+                } catch (AssertionError e) {
                     println 'Expected Result:'
                     println expectedResult
                     println 'Actual Result:'
@@ -137,13 +143,13 @@ class UserGuideSamplesRunner extends Runner {
 
             run.files.each { path ->
                 println "  checking file '$path' exists"
-                File file = new File(rootProjectDir, path).canonicalFile
+                def file = new File(run.executionDir, path).canonicalFile
                 Assert.assertTrue("Expected file '$file' does not exist.", file.exists())
                 Assert.assertTrue("Expected file '$file' is not a file.", file.isFile())
             }
             run.dirs.each { path ->
                 println "  checking directory '$path' exists"
-                File file = new File(rootProjectDir, path).canonicalFile
+                def file = new File(run.executionDir, path).canonicalFile
                 Assert.assertTrue("Expected directory '$file' does not exist.", file.exists())
                 Assert.assertTrue("Expected directory '$file' is not a directory.", file.isDirectory())
             }
@@ -153,33 +159,33 @@ class UserGuideSamplesRunner extends Runner {
     }
 
     private String replaceWithPlatformNewLines(String text) {
-        StringWriter stringWriter = new StringWriter()
+        def stringWriter = new StringWriter()
         new PlatformLineWriter(stringWriter).withWriter { it.write(text) }
         stringWriter.toString()
     }
 
     private String replaceWithRealSamplesDir(String text) {
-        def normalisedSamplesDir = TextUtil.normaliseFileSeparators(dist.samplesDir.absolutePath)
+        def normalisedSamplesDir = TextUtil.normaliseFileSeparators(baseExecutionDir.absolutePath)
         return text.replaceAll(Pattern.quote('/home/user/gradle/samples'), normalisedSamplesDir)
     }
 
-    static Collection<SampleRun> getScriptsForSamples(File userguideInfoDir) {
+    private Collection<SampleRun> getScriptsForSamples(File userguideInfoDir) {
         def samplesXml = new File(userguideInfoDir, 'samples.xml')
         assertSamplesGenerated(samplesXml.exists())
-        Node samples = new XmlParser().parse(samplesXml)
-        ListMultimap<String, GradleRun> samplesByDir = ArrayListMultimap.create()
+        def samples = new XmlParser().parse(samplesXml)
+        def samplesByDir = ArrayListMultimap.create()
 
         def children = samples.children()
         assertSamplesGenerated(!children.isEmpty())
 
         children.each {Node sample ->
-            String id = sample.'@id'
-            String dir = sample.'@dir'
-            String args = sample.'@args'
-            String outputFile = sample.'@outputFile'
+            def id = sample.'@id'
+            def dir = sample.'@dir'
+            def args = sample.'@args'
+            def outputFile = sample.'@outputFile'
             boolean ignoreExtraLines = Boolean.valueOf(sample.'@ignoreExtraLines')
 
-            GradleRun run = new GradleRun(id: id)
+            def run = new GradleRun(id: id)
             run.subDir = dir
             run.args = args ? args.split('\\s+') as List : []
             run.outputFile = outputFile
@@ -200,7 +206,7 @@ class UserGuideSamplesRunner extends Runner {
 
         // Remove duplicates for a given directory.
         samplesByDir.asMap().values().collect {List<GradleRun> dirSamples ->
-            Collection<GradleRun> runs = dirSamples.findAll {it.mustRun}
+            def runs = dirSamples.findAll { it.mustRun }
             if (!runs) {
                 // No samples in this dir have any args, so just run gradle tasks in the dir
                 def sample = dirSamples[0]
@@ -211,7 +217,7 @@ class UserGuideSamplesRunner extends Runner {
             }
         }.flatten().each { GradleRun run ->
             // Collect up into sample runs
-            SampleRun sampleRun = samplesById[run.id]
+            def sampleRun = samplesById[run.id]
             if (!sampleRun) {
                 sampleRun = new SampleRun(id: run.id, subDir: run.subDir)
                 samplesById[run.id] = sampleRun
@@ -222,30 +228,36 @@ class UserGuideSamplesRunner extends Runner {
         return samplesById.values()
     }
 
-    static void assertSamplesGenerated(boolean assertion) {
+    private void assertSamplesGenerated(boolean assertion) {
         assert assertion : """Couldn't find any samples. Most likely, samples.xml was not generated.
 Please run 'gradle docs:userguideDocbook' first"""
     }
-}
 
-class SampleRun {
-    String id
-    String subDir
-    List<GradleRun> runs = []
-}
+    private class GradleRun {
+        String id
+        List args = []
+        String subDir
+        Map envs = [:]
+        String outputFile
+        boolean expectFailure
+        boolean ignoreExtraLines
+        List files = []
+        List dirs = []
+
+        boolean getMustRun() {
+            return args || files || dirs
+        }
+
+        File getExecutionDir() {
+            new File(baseExecutionDir, subDir)
+        }
+    }
 
-class GradleRun {
-    String id
-    List args = []
-    String subDir
-    Map envs = [:]
-    String outputFile
-    boolean expectFailure
-    boolean ignoreExtraLines
-    List files = []
-    List dirs = []
-
-    boolean getMustRun() {
-        return args || files || dirs
+    private class SampleRun {
+        String id
+        String subDir
+        List<GradleRun> runs = []
     }
 }
+
+
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/AbstractDelegatingGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/AbstractDelegatingGradleExecuter.java
new file mode 100644
index 0000000..c1c3f86
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/AbstractDelegatingGradleExecuter.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures.executer;
+
+import org.gradle.launcher.daemon.registry.DaemonRegistry;
+import org.gradle.test.fixtures.file.TestDirectoryProvider;
+
+public abstract class AbstractDelegatingGradleExecuter extends AbstractGradleExecuter {
+
+    protected AbstractDelegatingGradleExecuter(GradleDistribution distribution, TestDirectoryProvider testDirectoryProvider) {
+        super(distribution, testDirectoryProvider);
+    }
+
+    @Override
+    protected ExecutionResult doRun() {
+        return configureExecuter().run();
+    }
+
+    @Override
+    protected ExecutionFailure doRunWithFailure() {
+        return configureExecuter().runWithFailure();
+    }
+
+    public DaemonRegistry getDaemonRegistry() {
+        return configureExecuter().getDaemonRegistry();
+    }
+
+    public void assertCanExecute() throws AssertionError {
+        configureExecuter().assertCanExecute();
+    }
+
+    @Override
+    public GradleHandle doStart() {
+        return configureExecuter().start();
+    }
+
+    protected abstract GradleExecuter configureExecuter();
+
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/AbstractGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/AbstractGradleExecuter.java
new file mode 100644
index 0000000..80c66d3
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/AbstractGradleExecuter.java
@@ -0,0 +1,602 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures.executer;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.internal.ClosureBackedAction;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.jvm.Jvm;
+import org.gradle.launcher.daemon.configuration.DaemonParameters;
+import org.gradle.listener.ActionBroadcast;
+import org.gradle.test.fixtures.file.TestDirectoryProvider;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.util.DeprecationLogger;
+import org.gradle.util.TextUtil;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.*;
+
+import static java.util.Arrays.asList;
+import static org.gradle.util.Matchers.*;
+
+public abstract class AbstractGradleExecuter implements GradleExecuter {
+
+    private final Logger logger;
+
+    private final IntegrationTestBuildContext buildContext = new IntegrationTestBuildContext();
+
+    private final List<String> args = new ArrayList<String>();
+    private final List<String> tasks = new ArrayList<String>();
+    private boolean allowExtraLogging = true;
+    private File workingDir;
+    private boolean quiet;
+    private boolean taskList;
+    private boolean dependencyList;
+    private boolean searchUpwards;
+    private Map<String, String> environmentVars = new HashMap<String, String>();
+    private List<File> initScripts = new ArrayList<File>();
+    private String executable;
+    private File gradleUserHomeDir = buildContext.getGradleUserHomeDir();
+    private File userHomeDir;
+    private File javaHome;
+    private File buildScript;
+    private File projectDir;
+    private File settingsFile;
+    private InputStream stdin;
+    private String defaultCharacterEncoding;
+    private Integer daemonIdleTimeoutSecs = 10;
+    private File daemonBaseDir = buildContext.getDaemonBaseDir();
+    private final List<String> gradleOpts = new ArrayList<String>();
+    private boolean noDefaultJvmArgs;
+    private boolean requireGradleHome;
+
+    private boolean deprecationChecksOn = true;
+    private boolean stackTraceChecksOn = true;
+
+    private final ActionBroadcast<GradleExecuter> beforeExecute = new ActionBroadcast<GradleExecuter>();
+
+    private final TestDirectoryProvider testDirectoryProvider;
+    private final GradleDistribution distribution;
+
+    protected AbstractGradleExecuter(GradleDistribution distribution, TestDirectoryProvider testDirectoryProvider) {
+        this.distribution = distribution;
+        this.testDirectoryProvider = testDirectoryProvider;
+        logger = Logging.getLogger(getClass());
+    }
+
+    protected Logger getLogger() {
+        return logger;
+    }
+
+    public GradleExecuter reset() {
+        args.clear();
+        tasks.clear();
+        initScripts.clear();
+        workingDir = null;
+        projectDir = null;
+        buildScript = null;
+        settingsFile = null;
+        quiet = false;
+        taskList = false;
+        dependencyList = false;
+        searchUpwards = false;
+        executable = null;
+        javaHome = null;
+        environmentVars.clear();
+        stdin = null;
+        defaultCharacterEncoding = null;
+        noDefaultJvmArgs = false;
+        deprecationChecksOn = true;
+        stackTraceChecksOn = true;
+        return this;
+    }
+
+
+    public GradleDistribution getDistribution() {
+        return distribution;
+    }
+
+    public TestDirectoryProvider getTestDirectoryProvider() {
+        return testDirectoryProvider;
+    }
+
+    public void beforeExecute(Action<? super GradleExecuter> action) {
+        beforeExecute.add(action);
+    }
+
+    public void beforeExecute(Closure action) {
+        beforeExecute.add(new ClosureBackedAction<GradleExecuter>(action));
+    }
+
+    public GradleExecuter inDirectory(File directory) {
+        workingDir = directory;
+        return this;
+    }
+
+    public File getWorkingDir() {
+        return workingDir == null ? getTestDirectoryProvider().getTestDirectory() : workingDir;
+    }
+
+    public GradleExecuter copyTo(GradleExecuter executer) {
+        executer.withGradleUserHomeDir(gradleUserHomeDir);
+        executer.withDaemonIdleTimeoutSecs(daemonIdleTimeoutSecs);
+        executer.withDaemonBaseDir(daemonBaseDir);
+
+        if (workingDir != null) {
+            executer.inDirectory(workingDir);
+        }
+        if (projectDir != null) {
+            executer.usingProjectDirectory(projectDir);
+        }
+        if (buildScript != null) {
+            executer.usingBuildScript(buildScript);
+        }
+        if (settingsFile != null) {
+            executer.usingSettingsFile(settingsFile);
+        }
+        if (javaHome != null) {
+            executer.withJavaHome(javaHome);
+        }
+        for (File initScript : initScripts) {
+            executer.usingInitScript(initScript);
+        }
+        executer.withTasks(tasks);
+        executer.withArguments(args);
+        executer.withEnvironmentVars(getAllEnvironmentVars());
+        executer.usingExecutable(executable);
+        if (quiet) {
+            executer.withQuietLogging();
+        }
+        if (taskList) {
+            executer.withTaskList();
+        }
+        if (dependencyList) {
+            executer.withDependencyList();
+        }
+
+        if (userHomeDir != null) {
+            executer.withUserHomeDir(userHomeDir);
+        }
+        if (stdin != null) {
+            executer.withStdIn(stdin);
+        }
+        if (defaultCharacterEncoding != null) {
+            executer.withDefaultCharacterEncoding(defaultCharacterEncoding);
+        }
+        executer.withGradleOpts(gradleOpts.toArray(new String[gradleOpts.size()]));
+        if (noDefaultJvmArgs) {
+            executer.withNoDefaultJvmArgs();
+        }
+        executer.setAllowExtraLogging(allowExtraLogging);
+
+        if (!deprecationChecksOn) {
+            executer.withDeprecationChecksDisabled();
+        }
+        if (!stackTraceChecksOn) {
+            executer.withStackTraceChecksDisabled();
+        }
+        executer.requireGradleHome(isRequireGradleHome());
+
+        return executer;
+    }
+
+    public GradleExecuter usingBuildScript(File buildScript) {
+        this.buildScript = buildScript;
+        return this;
+    }
+
+    public GradleExecuter usingProjectDirectory(File projectDir) {
+        this.projectDir = projectDir;
+        return this;
+    }
+
+    public GradleExecuter usingSettingsFile(File settingsFile) {
+        this.settingsFile = settingsFile;
+        return this;
+    }
+
+    public GradleExecuter usingInitScript(File initScript) {
+        initScripts.add(initScript);
+        return this;
+    }
+
+    public File getGradleUserHomeDir() {
+        return gradleUserHomeDir;
+    }
+
+    public GradleExecuter withGradleUserHomeDir(File userHomeDir) {
+        this.gradleUserHomeDir = userHomeDir;
+        return this;
+    }
+
+    public GradleExecuter requireOwnGradleUserHomeDir() {
+        return withGradleUserHomeDir(testDirectoryProvider.getTestDirectory().file("user-home"));
+    }
+
+    public File getUserHomeDir() {
+        return userHomeDir;
+    }
+
+    public List<String> getGradleOpts() {
+        return gradleOpts;
+    }
+
+    public GradleExecuter withUserHomeDir(File userHomeDir) {
+        this.userHomeDir = userHomeDir;
+        return this;
+    }
+
+    public File getJavaHome() {
+        return javaHome == null ? Jvm.current().getJavaHome() : javaHome;
+    }
+
+    public GradleExecuter withJavaHome(File javaHome) {
+        this.javaHome = javaHome;
+        return this;
+    }
+
+    public GradleExecuter usingExecutable(String script) {
+        this.executable = script;
+        return this;
+    }
+
+    public String getExecutable() {
+        return executable;
+    }
+
+    public GradleExecuter withStdIn(String text) {
+        this.stdin = new ByteArrayInputStream(TextUtil.toPlatformLineSeparators(text).getBytes());
+        return this;
+    }
+
+    public GradleExecuter withStdIn(InputStream stdin) {
+        this.stdin = stdin;
+        return this;
+    }
+
+    public InputStream getStdin() {
+        return stdin == null ? new ByteArrayInputStream(new byte[0]) : stdin;
+    }
+
+    public GradleExecuter withDefaultCharacterEncoding(String defaultCharacterEncoding) {
+        this.defaultCharacterEncoding = defaultCharacterEncoding;
+        return this;
+    }
+
+    public String getDefaultCharacterEncoding() {
+        return defaultCharacterEncoding == null ? Charset.defaultCharset().name() : defaultCharacterEncoding;
+    }
+
+    public GradleExecuter withSearchUpwards() {
+        searchUpwards = true;
+        return this;
+    }
+
+    public boolean isQuiet() {
+        return quiet;
+    }
+
+    public GradleExecuter withQuietLogging() {
+        quiet = true;
+        return this;
+    }
+
+    public GradleExecuter withTaskList() {
+        taskList = true;
+        return this;
+    }
+
+    public GradleExecuter withDependencyList() {
+        dependencyList = true;
+        return this;
+    }
+
+    public GradleExecuter withArguments(String... args) {
+        return withArguments(Arrays.asList(args));
+    }
+
+    public GradleExecuter withArguments(List<String> args) {
+        this.args.clear();
+        this.args.addAll(args);
+        return this;
+    }
+
+    public GradleExecuter withArgument(String arg) {
+        this.args.add(arg);
+        return this;
+    }
+
+    public GradleExecuter withEnvironmentVars(Map<String, ?> environment) {
+        environmentVars.clear();
+        for (Map.Entry<String, ?> entry : environment.entrySet()) {
+            if (entry.getKey().equals("GRADLE_OPTS")) {
+                throw new IllegalArgumentException("GRADLE_OPTS cannot be set via withEnvironmentVars(), use withGradleOpts()");
+            }
+            environmentVars.put(entry.getKey(), entry.getValue().toString());
+        }
+        return this;
+    }
+
+    protected Map<String, String> getAllEnvironmentVars() {
+        return environmentVars;
+    }
+
+    public Map<String, String> getEnvironmentVars() {
+        return environmentVars;
+    }
+
+    public GradleExecuter withTasks(String... names) {
+        return withTasks(Arrays.asList(names));
+    }
+
+    public GradleExecuter withTasks(List<String> names) {
+        tasks.clear();
+        tasks.addAll(names);
+        return this;
+    }
+
+    public GradleExecuter withDaemonIdleTimeoutSecs(int secs) {
+        daemonIdleTimeoutSecs = secs;
+        return this;
+    }
+
+    public GradleExecuter withNoDefaultJvmArgs() {
+        noDefaultJvmArgs = true;
+        return this;
+    }
+
+    public GradleExecuter withDaemonBaseDir(File daemonBaseDir) {
+        this.daemonBaseDir = daemonBaseDir;
+        return this;
+    }
+
+    public GradleExecuter requireIsolatedDaemons() {
+        return withDaemonBaseDir(testDirectoryProvider.getTestDirectory().file("daemon"));
+    }
+
+    protected File getDaemonBaseDir() {
+        return daemonBaseDir;
+    }
+
+    protected boolean isNoDefaultJvmArgs() {
+        return noDefaultJvmArgs;
+    }
+
+    protected List<String> getAllArgs() {
+        List<String> allArgs = new ArrayList<String>();
+        if (buildScript != null) {
+            allArgs.add("--build-file");
+            allArgs.add(buildScript.getAbsolutePath());
+        }
+        if (projectDir != null) {
+            allArgs.add("--project-dir");
+            allArgs.add(projectDir.getAbsolutePath());
+        }
+        for (File initScript : initScripts) {
+            allArgs.add("--init-script");
+            allArgs.add(initScript.getAbsolutePath());
+        }
+        if (settingsFile != null) {
+            allArgs.add("--settings-file");
+            allArgs.add(settingsFile.getAbsolutePath());
+        }
+        if (quiet) {
+            allArgs.add("--quiet");
+        }
+        if (taskList) {
+            allArgs.add("tasks");
+        }
+        if (dependencyList) {
+            allArgs.add("dependencies");
+        }
+
+        if (!searchUpwards) {
+            boolean settingsFoundAboveInTestDir = false;
+            TestFile dir = new TestFile(getWorkingDir());
+            while (dir != null && getTestDirectoryProvider().getTestDirectory().isSelfOrDescendent(dir)) {
+                if (dir.file("settings.gradle").isFile()) {
+                    settingsFoundAboveInTestDir = true;
+                    break;
+                }
+                dir = dir.getParentFile();
+            }
+
+            if (!settingsFoundAboveInTestDir) {
+                allArgs.add("--no-search-upward");
+            }
+        }
+
+        // This will cause problems on Windows if the path to the Gradle executable that is used has a space in it (e.g. the user's dir is c:/Users/Luke Daley/)
+        // This is fundamentally a windows issue: You can't have arguments with spaces in them if the path to the batch script has a space
+        // We could work around this by setting -Dgradle.user.home but GRADLE-1730 (which affects 1.0-milestone-3) means that that
+        // is problematic as well. For now, we just don't support running the int tests from a path with a space in it on Windows.
+        // When we stop testing against M3 we should change to use the system property.
+        if (getGradleUserHomeDir() != null) {
+            allArgs.add("--gradle-user-home");
+            allArgs.add(getGradleUserHomeDir().getAbsolutePath());
+        }
+
+        allArgs.addAll(args);
+        allArgs.addAll(tasks);
+        return allArgs;
+    }
+
+    protected Map<String, String> getImplicitJvmSystemProperties() {
+        Map<String, String> properties = new LinkedHashMap<String, String>();
+
+        if (getUserHomeDir() != null) {
+            properties.put("user.home", getUserHomeDir().getAbsolutePath());
+        }
+
+        properties.put(DaemonParameters.IDLE_TIMEOUT_SYS_PROPERTY, "" + (daemonIdleTimeoutSecs * 1000));
+        properties.put(DaemonParameters.BASE_DIR_SYS_PROPERTY, daemonBaseDir.getAbsolutePath());
+        properties.put(DeprecationLogger.ORG_GRADLE_DEPRECATION_TRACE_PROPERTY_NAME, "true");
+
+        properties.put("java.io.tmpdir", getTmpDir().createDir().getAbsolutePath());
+        return properties;
+    }
+
+    public final GradleHandle start() {
+        fireBeforeExecute();
+        assertCanExecute();
+        return doStart();
+    }
+
+    public final ExecutionResult run() {
+        fireBeforeExecute();
+        assertCanExecute();
+        try {
+            return checkResult(doRun());
+        } finally {
+            reset();
+        }
+    }
+
+    public final ExecutionFailure runWithFailure() {
+        fireBeforeExecute();
+        assertCanExecute();
+        try {
+            return checkResult(doRunWithFailure());
+        } finally {
+            reset();
+        }
+    }
+
+    private void fireBeforeExecute() {
+        beforeExecute.execute(this);
+    }
+
+    protected GradleHandle doStart() {
+        throw new UnsupportedOperationException(String.format("%s does not support running asynchronously.", getClass().getSimpleName()));
+    }
+
+    protected abstract ExecutionResult doRun();
+
+    protected abstract ExecutionFailure doRunWithFailure();
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractGradleExecuter withGradleOpts(String... gradleOpts) {
+        this.gradleOpts.addAll(asList(gradleOpts));
+        return this;
+    }
+
+    protected <T extends ExecutionResult> T checkResult(T result) {
+        if (stackTraceChecksOn) {
+            // Assert that nothing unexpected was logged
+            assertOutputHasNoStackTraces(result);
+            assertErrorHasNoStackTraces(result);
+        }
+        if (deprecationChecksOn) {
+            assertOutputHasNoDeprecationWarnings(result);
+        }
+
+        if (getExecutable() == null) {
+            // Assert that no temp files are left lying around
+            // Note: don't do this if a custom executable is used, as we don't know (and probably don't care) whether the executable cleans up or not
+            TestFile[] testFiles = getTmpDir().listFiles();
+            if (testFiles != null) {
+                List<String> unexpectedFiles = new ArrayList<String>();
+                for (File file : testFiles) {
+                    if (!file.getName().matches("maven-artifact\\d+.tmp")) {
+                        unexpectedFiles.add(file.getName());
+                    }
+                }
+//            Assert.assertThat(unexpectedFiles, Matchers.isEmpty());
+            }
+        }
+
+        return result;
+    }
+
+    private void assertOutputHasNoStackTraces(ExecutionResult result) {
+        assertNoStackTraces(result.getOutput(), "Standard output");
+    }
+
+    public void assertErrorHasNoStackTraces(ExecutionResult result) {
+        String error = result.getError();
+        if (result instanceof ExecutionFailure) {
+            // Axe everything after the expected exception
+            int pos = error.indexOf("* Exception is:" + TextUtil.getPlatformLineSeparator());
+            if (pos >= 0) {
+                error = error.substring(0, pos);
+            }
+        }
+        assertNoStackTraces(error, "Standard error");
+    }
+
+    public void assertOutputHasNoDeprecationWarnings(ExecutionResult result) {
+        assertNoDeprecationWarnings(result.getOutput(), "Standard output");
+        assertNoDeprecationWarnings(result.getError(), "Standard error");
+    }
+
+    private void assertNoDeprecationWarnings(String output, String displayName) {
+        boolean javacWarning = containsLine(matchesRegexp(".*use(s)? or override(s)? a deprecated API\\.")).matches(output);
+        boolean deprecationWarning = containsLine(matchesRegexp(".* deprecated.*")).matches(output);
+        if (deprecationWarning && !javacWarning) {
+            throw new AssertionError(String.format("%s contains a deprecation warning:%n=====%n%s%n=====%n", displayName, output));
+        }
+    }
+
+    private void assertNoStackTraces(String output, String displayName) {
+        if (containsLine(matchesRegexp("\\s+(at\\s+)?[\\w.$_]+\\([\\w._]+:\\d+\\)")).matches(output)) {
+            throw new AssertionError(String.format("%s contains an unexpected stack trace:%n=====%n%s%n=====%n", displayName, output));
+        }
+    }
+
+    public GradleExecuter withDeprecationChecksDisabled() {
+        deprecationChecksOn = false;
+        // turn off stack traces too
+        stackTraceChecksOn = false;
+        return this;
+    }
+
+    public GradleExecuter withStackTraceChecksDisabled() {
+        stackTraceChecksOn = false;
+        return this;
+    }
+
+    protected TestFile getTmpDir() {
+        return new TestFile(getTestDirectoryProvider().getTestDirectory(), "tmp");
+    }
+
+    /**
+     * set true to allow the executer to increase the log level if necessary to help out debugging. Set false to make the executer never update the log level.
+     */
+    public GradleExecuter setAllowExtraLogging(boolean allowExtraLogging) {
+        this.allowExtraLogging = allowExtraLogging;
+        return this;
+    }
+
+    public boolean isAllowExtraLogging() {
+        return allowExtraLogging;
+    }
+
+    public boolean isRequireGradleHome() {
+        return requireGradleHome;
+    }
+
+    public GradleExecuter requireGradleHome(boolean requireGradleHome) {
+        this.requireGradleHome = requireGradleHome;
+        return this;
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ArtifactBuilder.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ArtifactBuilder.java
new file mode 100644
index 0000000..02e9033
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ArtifactBuilder.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures.executer;
+
+import org.gradle.test.fixtures.file.TestFile;
+
+import java.io.File;
+
+public interface ArtifactBuilder {
+    TestFile sourceFile(String path);
+
+    TestFile resourceFile(String path);
+
+    void buildJar(File jarFile);
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DaemonGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DaemonGradleExecuter.java
new file mode 100644
index 0000000..bb0334e
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DaemonGradleExecuter.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures.executer;
+
+import org.gradle.api.JavaVersion;
+import org.gradle.api.Transformer;
+import org.gradle.test.fixtures.file.TestDirectoryProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.util.Arrays.asList;
+import static org.apache.commons.collections.CollectionUtils.containsAny;
+import static org.gradle.util.CollectionUtils.collect;
+import static org.gradle.util.CollectionUtils.join;
+
+public class DaemonGradleExecuter extends ForkingGradleExecuter {
+
+    public DaemonGradleExecuter(GradleDistribution distribution, TestDirectoryProvider testDirectoryProvider) {
+        super(distribution, testDirectoryProvider);
+    }
+
+    @Override
+    protected List<String> getAllArgs() {
+        List<String> args = new ArrayList<String>(super.getAllArgs());
+        args.add(0, "--daemon");
+
+        if(!isQuiet() && isAllowExtraLogging()) {
+            if (!containsAny(args, asList("-i", "--info", "-d", "--debug", "-q", "--quiet"))) {
+                args.add(0, "-i");
+            }
+        }
+
+        // Workaround for http://issues.gradle.org/browse/GRADLE-2625
+        if (getUserHomeDir() != null) {
+            args.add(String.format("-Duser.home=%s", getUserHomeDir().getPath()));
+        }
+
+        return args;
+    }
+
+    @Override
+    public List<String> getGradleOpts() {
+        if (isNoDefaultJvmArgs()) {
+            return super.getGradleOpts();
+        } else {
+            // Workaround for http://issues.gradle.org/browse/GRADLE-2629
+            // Instead of just adding these as standalone opts, we need to add to
+            // -Dorg.gradle.jvmArgs in order for them to be effectual
+            List<String> jvmArgs = new ArrayList<String>(4);
+            jvmArgs.add("-XX:MaxPermSize=256m");
+            jvmArgs.add("-XX:+HeapDumpOnOutOfMemoryError");
+            if (JavaVersion.current().isJava5()) {
+                jvmArgs.add("-XX:+CMSPermGenSweepingEnabled");
+                jvmArgs.add("-Dcom.sun.management.jmxremote");
+            }
+
+            String quotedArgs = join(" ", collect(jvmArgs, new Transformer<String, String>() {
+                public String transform(String input) {
+                    return String.format("'%s'", input);
+                }
+            }));
+
+            List<String> gradleOpts = new ArrayList<String>(super.getGradleOpts());
+            gradleOpts.add(String.format("-Dorg.gradle.jvmArgs=%s", quotedArgs));
+            return gradleOpts;
+        }
+    }
+
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DefaultGradleDistribution.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DefaultGradleDistribution.java
new file mode 100644
index 0000000..e3740be
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DefaultGradleDistribution.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.executer;
+
+import org.gradle.internal.jvm.Jvm;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.test.fixtures.file.TestDirectoryProvider;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.util.GradleVersion;
+
+public class DefaultGradleDistribution implements GradleDistribution {
+
+    private final GradleVersion version;
+    private final TestFile gradleHomeDir;
+    private final TestFile binDistribution;
+
+    public DefaultGradleDistribution(GradleVersion gradleVersion, TestFile gradleHomeDir, TestFile binDistribution) {
+        this.version = gradleVersion;
+        this.gradleHomeDir = gradleHomeDir;
+        this.binDistribution = binDistribution;
+    }
+
+    public TestFile getGradleHomeDir() {
+        return gradleHomeDir;
+    }
+
+    public TestFile getBinDistribution() {
+        return binDistribution;
+    }
+
+    public GradleVersion getVersion() {
+        return version;
+    }
+
+    public GradleExecuter executer(TestDirectoryProvider testDirectoryProvider) {
+        return new ForkingGradleExecuter(this, testDirectoryProvider);
+    }
+
+    public boolean worksWith(Jvm jvm) {
+        // Milestone 4 was broken on the IBM jvm
+        if (jvm.isIbmJvm() && version == GradleVersion.version("1.0-milestone-4")) {
+            return false;
+        }
+        // 0.9-rc-1 was broken for Java 5
+        if (version == GradleVersion.version("0.9-rc-1")) {
+            return jvm.getJavaVersion().isJava6Compatible();
+        }
+
+        return jvm.getJavaVersion().isJava5Compatible();
+    }
+
+    public boolean worksWith(OperatingSystem os) {
+        // 1.0-milestone-5 was broken where jna was not available
+        //noinspection SimplifiableIfStatement
+        if (version == GradleVersion.version("1.0-milestone-5")) {
+            return os.isWindows() || os.isMacOsX() || os.isLinux();
+        } else {
+            return true;
+        }
+    }
+
+    public boolean isDaemonSupported() {
+        // Milestone 7 was broken on the IBM jvm
+        if (Jvm.current().isIbmJvm() && version == GradleVersion.version("1.0-milestone-7")) {
+            return false;
+        }
+
+        if (OperatingSystem.current().isWindows()) {
+            // On windows, daemon is ok for anything > 1.0-milestone-3
+            return isSameOrNewer("1.0-milestone-4");
+        } else {
+            // Daemon is ok for anything >= 0.9
+            return isSameOrNewer("0.9");
+        }
+    }
+
+    public boolean isDaemonIdleTimeoutConfigurable() {
+        return isSameOrNewer("1.0-milestone-7");
+    }
+
+    public boolean isOpenApiSupported() {
+        return isSameOrNewer("0.9-rc-1");
+    }
+
+    public boolean isToolingApiSupported() {
+        return isSameOrNewer("1.0-milestone-3");
+    }
+
+    public int getArtifactCacheLayoutVersion() {
+        if (isSameOrNewer("1.4-rc-1")) {
+            return 23;
+        } else if (isSameOrNewer("1.3")) {
+            return 15;
+        } else {
+            return 1;
+        }
+    }
+
+    public boolean wrapperCanExecute(GradleVersion version) {
+        if (version.equals(GradleVersion.version("0.8")) || isVersion("0.8")) {
+            // There was a breaking change after 0.8
+            return false;
+        }
+        if (isVersion("0.9.1")) {
+            // 0.9.1 couldn't handle anything with a timestamp whose timezone was behind GMT
+            return version.getVersion().matches(".*+\\d{4}");
+        }
+        if (isSameOrNewer("0.9.2") && isSameOrOlder("1.0-milestone-2")) {
+            // These versions couldn't handle milestone patches
+            if (version.getVersion().matches("1.0-milestone-\\d+[a-z]-.+")) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean isSupportsSpacesInGradleAndJavaOpts() {
+        return isSameOrNewer("1.0-milestone-5");
+    }
+
+    protected boolean isSameOrNewer(String otherVersion) {
+        return isVersion(otherVersion) || version.compareTo(GradleVersion.version(otherVersion)) > 0;
+    }
+
+    protected boolean isSameOrOlder(String otherVersion) {
+        return isVersion(otherVersion) || version.compareTo(GradleVersion.version(otherVersion)) <= 0;
+    }
+
+    protected boolean isVersion(String otherVersionString) {
+        GradleVersion otherVersion = GradleVersion.version(otherVersionString);
+        return version.compareTo(otherVersion) == 0 || (version.isSnapshot() && version.getVersionBase().equals(otherVersion.getVersionBase()));
+    }
+
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DependencyResolutionFailure.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DependencyResolutionFailure.groovy
new file mode 100644
index 0000000..55e0dd1
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DependencyResolutionFailure.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.executer
+
+import static org.gradle.util.Matchers.*;
+
+/**
+ * by Szczepan Faber, created at: 12/12/12
+ */
+public class DependencyResolutionFailure {
+    private final ExecutionFailure failure
+
+    DependencyResolutionFailure(ExecutionFailure failure) {
+        this.failure = failure
+    }
+
+    DependencyResolutionFailure assertFailedConfiguration(String configuration) {
+        failure.assertHasCause("Could not resolve all dependencies for configuration '$configuration'.")
+        this
+    }
+
+    DependencyResolutionFailure assertFailedDependencyRequiredBy(String dependency) {
+        failure.assertThatCause(matchesRegexp("(?ms).*Required by:\\s+$dependency.*"))
+        this
+    }
+
+    DependencyResolutionFailure assertHasCause(String cause) {
+        failure.assertHasCause(cause)
+        this
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DetailedExecutionFailure.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DetailedExecutionFailure.groovy
new file mode 100644
index 0000000..af93967
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DetailedExecutionFailure.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.executer
+
+import static org.hamcrest.Matchers.startsWith
+
+/**
+ * by Szczepan Faber, created at: 11/26/12
+ */
+class DetailedExecutionFailure {
+    ExecutionFailure failure
+
+    public DetailedExecutionFailure(ExecutionFailure failure) {
+        this.failure = failure;
+    }
+
+    public assertTestsFailed() {
+        failure
+            .assertHasDescription("Execution failed for task ':test'.")
+            .assertThatCause(startsWith("There were failing tests."));
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/EmbeddedDaemonGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/EmbeddedDaemonGradleExecuter.java
new file mode 100644
index 0000000..d3b1ff6
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/EmbeddedDaemonGradleExecuter.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures.executer;
+
+import org.gradle.StartParameter;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.initialization.DefaultCommandLineConverter;
+import org.gradle.launcher.cli.ExecuteBuildAction;
+import org.gradle.launcher.daemon.client.DaemonClient;
+import org.gradle.launcher.daemon.client.EmbeddedDaemonClientServices;
+import org.gradle.launcher.daemon.registry.DaemonRegistry;
+import org.gradle.launcher.exec.BuildActionParameters;
+import org.gradle.launcher.exec.DefaultBuildActionParameters;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.LoggingServiceRegistry;
+import org.gradle.logging.internal.StreamBackedStandardOutputListener;
+import org.gradle.test.fixtures.file.TestDirectoryProvider;
+
+import java.lang.management.ManagementFactory;
+
+class EmbeddedDaemonGradleExecuter extends AbstractGradleExecuter {
+
+    private final EmbeddedDaemonClientServices daemonClientServices = new EmbeddedDaemonClientServices(LoggingServiceRegistry.newEmbeddableLogging(), false);
+
+    EmbeddedDaemonGradleExecuter(GradleDistribution distribution, TestDirectoryProvider testDirectoryProvider) {
+        super(distribution, testDirectoryProvider);
+    }
+
+    public DaemonRegistry getDaemonRegistry() {
+        return daemonClientServices.get(DaemonRegistry.class);
+    }
+
+    public void assertCanExecute() throws AssertionError {
+    }
+
+    protected ExecutionResult doRun() {
+        return doRun(false);
+    }
+
+    protected ExecutionFailure doRunWithFailure() {
+        return (ExecutionFailure)doRun(true);
+    }
+
+    protected ExecutionResult doRun(boolean expectFailure) {
+        StringBuilder output = new StringBuilder();
+        StringBuilder error = new StringBuilder();
+
+        LoggingManagerInternal loggingManager = createLoggingManager(output, error);
+        loggingManager.start();
+
+        ExecuteBuildAction buildAction = createBuildAction();
+        BuildActionParameters buildActionParameters = createBuildActionParameters();
+        DaemonClient daemonClient = daemonClientServices.get(DaemonClient.class);
+
+        Exception failure = null;
+        try {
+            daemonClient.execute(buildAction, buildActionParameters);
+        } catch (Exception e) {
+            failure = e;
+        } finally {
+            daemonClient.stop();
+            loggingManager.stop();
+        }
+
+        boolean didFail = failure != null;
+        if (expectFailure != didFail) {
+            String didOrDidntSnippet = didFail ? "DID fail" : "DID NOT fail";
+            throw new RuntimeException(String.format("Gradle execution in %s %s with: %nOutput:%n%s%nError:%n%s%n-----%n", getWorkingDir(), didOrDidntSnippet, output, error), failure);
+        }
+
+        if (expectFailure) {
+            return new OutputScrapingExecutionFailure(output.toString(), error.toString());
+        } else {
+            return new OutputScrapingExecutionResult(output.toString(), error.toString());
+        }
+    }
+
+    private LoggingManagerInternal createLoggingManager(StringBuilder output, StringBuilder error) {
+        LoggingManagerInternal loggingManager = daemonClientServices.getLoggingServices().newInstance(LoggingManagerInternal.class);
+        loggingManager.addStandardOutputListener(new StreamBackedStandardOutputListener(output));
+        loggingManager.addStandardErrorListener(new StreamBackedStandardOutputListener(error));
+        return loggingManager;
+    }
+
+    private ExecuteBuildAction createBuildAction() {
+        DefaultCommandLineConverter commandLineConverter = new DefaultCommandLineConverter();
+        StartParameter startParameter = new StartParameter();
+        startParameter.setCurrentDir(getWorkingDir());
+        commandLineConverter.convert(getAllArgs(), startParameter);
+        return new ExecuteBuildAction(startParameter);
+    }
+
+    private BuildActionParameters createBuildActionParameters() {
+        return new DefaultBuildActionParameters(daemonClientServices.get(BuildClientMetaData.class), getStartTime(), System.getProperties(), getEnvironmentVars(), getWorkingDir(), LogLevel.LIFECYCLE);
+    }
+
+    private long getStartTime() {
+        return ManagementFactory.getRuntimeMXBean().getStartTime();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionFailure.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionFailure.java
new file mode 100644
index 0000000..9a3737f
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionFailure.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures.executer;
+
+import org.hamcrest.Matcher;
+
+public interface ExecutionFailure extends ExecutionResult {
+    ExecutionFailure assertHasLineNumber(int lineNumber);
+
+    ExecutionFailure assertHasFileName(String filename);
+
+    ExecutionFailure assertHasCause(String description);
+
+    ExecutionFailure assertThatCause(Matcher<String> matcher);
+
+    ExecutionFailure assertHasDescription(String context);
+
+    ExecutionFailure assertThatDescription(Matcher<String> matcher);
+
+    ExecutionFailure assertHasNoCause();
+
+    ExecutionFailure assertTestsFailed();
+
+    DependencyResolutionFailure getDependencyResolutionFailure(); //TODO SF use this fixture where possible
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionResult.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionResult.java
new file mode 100644
index 0000000..ccb0382
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionResult.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures.executer;
+
+import java.util.List;
+import java.util.Set;
+
+public interface ExecutionResult {
+    String getOutput();
+
+    String getError();
+
+    ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines);
+
+    /**
+     * Returns the tasks have been executed in order (includes tasks that were skipped). Note: ignores buildSrc tasks.
+     */
+    List<String> getExecutedTasks();
+    
+    /**
+     * Asserts that exactly the given set of tasks have been executed in the given order. Note: ignores buildSrc tasks.
+     */
+    ExecutionResult assertTasksExecuted(String... taskPaths);
+
+    /**
+     * Asserts that exactly the given set of projects have been evaluated in the given order.
+     */
+    ExecutionResult assertProjectsEvaluated(String... projectPaths);
+
+    /**
+     * Returns the tasks that were skipped, in an undefined order. Note: ignores buildSrc tasks.
+     */
+    Set<String> getSkippedTasks();
+    
+    /**
+     * Asserts that exactly the given set of tasks have been skipped. Note: ignores buildSrc tasks.
+     */
+    ExecutionResult assertTasksSkipped(String... taskPaths);
+
+    /**
+     * Asserts the given task has been skipped. Note: ignores buildSrc tasks.
+     */
+    ExecutionResult assertTaskSkipped(String taskPath);
+
+    /**
+     * Asserts that exactly the given set of tasks have not been skipped. Note: ignores buildSrc tasks.
+     */
+    ExecutionResult assertTasksNotSkipped(String... taskPaths);
+
+    /**
+     * Asserts that the given task has not been skipped. Note: ignores buildSrc tasks.
+     */
+    ExecutionResult assertTaskNotSkipped(String taskPath);
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ForkingGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ForkingGradleExecuter.java
new file mode 100644
index 0000000..532af38
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ForkingGradleExecuter.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.executer;
+
+import org.gradle.internal.Factory;
+import org.gradle.internal.nativeplatform.jna.WindowsHandlesManipulator;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.launcher.daemon.registry.DaemonRegistry;
+import org.gradle.launcher.daemon.registry.DaemonRegistryServices;
+import org.gradle.process.internal.ExecHandleBuilder;
+import org.gradle.test.fixtures.file.TestDirectoryProvider;
+import org.gradle.test.fixtures.file.TestFile;
+
+import java.io.File;
+import java.util.*;
+
+import static org.junit.Assert.fail;
+
+class ForkingGradleExecuter extends AbstractGradleExecuter {
+
+    public ForkingGradleExecuter(GradleDistribution distribution, TestDirectoryProvider testDirectoryProvider) {
+        super(distribution, testDirectoryProvider);
+    }
+
+    public DaemonRegistry getDaemonRegistry() {
+        return new DaemonRegistryServices(getDaemonBaseDir()).get(DaemonRegistry.class);
+    }
+
+    public void assertCanExecute() throws AssertionError {
+        // Can run any build
+    }
+
+    @Override
+    protected List<String> getAllArgs() {
+        List<String> args = new ArrayList<String>();
+        args.addAll(super.getAllArgs());
+        args.add("--stacktrace");
+        return args;
+    }
+
+    private ExecHandleBuilder createExecHandleBuilder() {
+        TestFile gradleHomeDir = getDistribution().getGradleHomeDir();
+        if (!gradleHomeDir.isDirectory()) {
+            fail(gradleHomeDir + " is not a directory.\n"
+                    + "If you are running tests from IDE make sure that gradle tasks that prepare the test image were executed. Last time it was 'intTestImage' task.");
+        }
+
+        ExecHandleBuilder builder = new ExecHandleBuilder() {
+            @Override
+            public File getWorkingDir() {
+                // Override this, so that the working directory is not canonicalised. Some int tests require that
+                // the working directory is not canonicalised
+                return ForkingGradleExecuter.this.getWorkingDir();
+            }
+        };
+
+        // Override some of the user's environment
+        builder.environment("GRADLE_HOME", "");
+        builder.environment("JAVA_HOME", getJavaHome());
+        builder.environment("GRADLE_OPTS", getGradleOptsString());
+        builder.environment("JAVA_OPTS", "");
+
+        builder.environment(getAllEnvironmentVars());
+        builder.workingDir(getWorkingDir());
+        builder.setStandardInput(getStdin());
+
+        builder.args(getAllArgs());
+
+        ExecHandlerConfigurer configurer = OperatingSystem.current().isWindows() ? new WindowsConfigurer() : new UnixConfigurer();
+        configurer.configure(builder);
+
+        getLogger().info(String.format("Execute in %s with: %s %s", builder.getWorkingDir(), builder.getExecutable(), builder.getArgs()));
+
+        return builder;
+    }
+
+    @Override
+    public GradleHandle doStart() {
+        return createGradleHandle(getDefaultCharacterEncoding(), new Factory<ExecHandleBuilder>() {
+            public ExecHandleBuilder create() {
+                return createExecHandleBuilder();
+            }
+        }).start();
+    }
+
+    protected ForkingGradleHandle createGradleHandle(String encoding, Factory<ExecHandleBuilder> execHandleFactory) {
+        return new ForkingGradleHandle(encoding, execHandleFactory);
+    }
+
+    protected ExecutionResult doRun() {
+        return start().waitForFinish();
+    }
+
+    protected ExecutionFailure doRunWithFailure() {
+        return start().waitForFailure();
+    }
+
+    @Override
+    public List<String> getGradleOpts() {
+        List<String> gradleOpts = new ArrayList<java.lang.String>(super.getGradleOpts());
+        for (Map.Entry<String, String> entry : getImplicitJvmSystemProperties().entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue();
+
+            if (value.contains(" ") && !getDistribution().isSupportsSpacesInGradleAndJavaOpts()) {
+                getLogger().warn("Removing '{}' from GRADLE_OPTS (value: {}) as it contains spaces, and this Gradle version ({}) cannot handle spaces in GRADLE_OPTS",
+                        key, value, getDistribution().getVersion().getVersion()
+                );
+                continue;
+            }
+
+            gradleOpts.add(String.format("-D%s=%s", key, value));
+        }
+
+        gradleOpts.add("-ea");
+
+        //uncomment for debugging
+//        gradleOpts.add("-Xdebug");
+//        gradleOpts.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005");
+
+        return gradleOpts;
+    }
+
+    @Override
+    protected Map<String, String> getImplicitJvmSystemProperties() {
+        Map<String, String> implicitJvmSystemProperties = new LinkedHashMap<String, String>(super.getImplicitJvmSystemProperties());
+        implicitJvmSystemProperties.put("file.encoding", getDefaultCharacterEncoding());
+        return implicitJvmSystemProperties;
+    }
+
+    private String getGradleOptsString() {
+        StringBuilder result = new StringBuilder();
+        for (String gradleOpt : getGradleOpts()) {
+            if (result.length() > 0) {
+                result.append(" ");
+            }
+            if (gradleOpt.contains(" ")) {
+                assert !gradleOpt.contains("\"");
+                result.append('"');
+                result.append(gradleOpt);
+                result.append('"');
+            } else {
+                result.append(gradleOpt);
+            }
+        }
+
+        return result.toString();
+    }
+
+    private interface ExecHandlerConfigurer {
+        void configure(ExecHandleBuilder builder);
+    }
+
+    private class WindowsConfigurer implements ExecHandlerConfigurer {
+        public void configure(ExecHandleBuilder builder) {
+            String cmd;
+            if (getExecutable() != null) {
+                cmd = getExecutable().replace('/', File.separatorChar);
+            } else {
+                cmd = "gradle";
+            }
+            builder.executable("cmd");
+
+            List<String> allArgs = builder.getArgs();
+            builder.setArgs(Arrays.asList("/c", cmd));
+            builder.args(allArgs);
+
+            String gradleHome = getDistribution().getGradleHomeDir().getAbsolutePath();
+
+            // NOTE: Windows uses Path, but allows asking for PATH, and PATH
+            //       is set within builder object for some things such
+            //       as CommandLineIntegrationTest, try PATH first, and
+            //       then revert to default of Path if null
+            Object path = builder.getEnvironment().get("PATH");
+            if (path == null) {
+                path = builder.getEnvironment().get("Path");
+            }
+            builder.environment("Path", String.format("%s\\bin;%s", gradleHome, path));
+            builder.environment("GRADLE_EXIT_CONSOLE", "true");
+
+            getLogger().info("Initializing windows process so that child process will be fully detached...");
+            new WindowsHandlesManipulator().uninheritStandardStreams();
+        }
+    }
+
+    private class UnixConfigurer implements ExecHandlerConfigurer {
+        public void configure(ExecHandleBuilder builder) {
+            if (getExecutable() != null) {
+                File exe = new File(getExecutable());
+                if (exe.isAbsolute()) {
+                    builder.executable(exe.getAbsolutePath());
+                } else {
+                    builder.executable(String.format("%s/%s", getWorkingDir().getAbsolutePath(), getExecutable()));
+                }
+            } else {
+                builder.executable(String.format("%s/bin/gradle", getDistribution().getGradleHomeDir().getAbsolutePath()));
+            }
+        }
+    }
+
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ForkingGradleHandle.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ForkingGradleHandle.java
new file mode 100644
index 0000000..9439dfb
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ForkingGradleHandle.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures.executer;
+
+import org.apache.commons.io.output.CloseShieldOutputStream;
+import org.apache.commons.io.output.TeeOutputStream;
+import org.gradle.internal.Factory;
+import org.gradle.internal.UncheckedException;
+import org.gradle.process.ExecResult;
+import org.gradle.process.internal.AbstractExecHandleBuilder;
+import org.gradle.process.internal.ExecHandle;
+import org.gradle.process.internal.ExecHandleState;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+
+class ForkingGradleHandle extends OutputScrapingGradleHandle {
+    final private Factory<? extends AbstractExecHandleBuilder> execHandleFactory;
+
+    final private ByteArrayOutputStream standardOutput = new ByteArrayOutputStream();
+    final private ByteArrayOutputStream errorOutput = new ByteArrayOutputStream();
+
+    private ExecHandle execHandle;
+    private final String outputEncoding;
+
+    public ForkingGradleHandle(String outputEncoding, Factory<? extends AbstractExecHandleBuilder> execHandleFactory) {
+        this.execHandleFactory = execHandleFactory;
+        this.outputEncoding = outputEncoding;
+    }
+
+    public String getStandardOutput() {
+        try {
+            return standardOutput.toString(outputEncoding);
+        } catch (UnsupportedEncodingException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public String getErrorOutput() {
+        try {
+            return errorOutput.toString(outputEncoding);
+        } catch (UnsupportedEncodingException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public GradleHandle start() {
+        if (execHandle != null) {
+            throw new IllegalStateException("you have already called start() on this handle");
+        }
+
+        AbstractExecHandleBuilder execBuilder = execHandleFactory.create();
+        execBuilder.setStandardOutput(new CloseShieldOutputStream(new TeeOutputStream(System.out, standardOutput)));
+        execBuilder.setErrorOutput(new CloseShieldOutputStream(new TeeOutputStream(System.err, errorOutput)));
+        execHandle = execBuilder.build();
+
+        execHandle.start();
+
+        return this;
+    }
+
+    public GradleHandle abort() {
+        getExecHandle().abort();
+        return this;
+    }
+
+    public boolean isRunning() {
+        return execHandle != null && execHandle.getState() == ExecHandleState.STARTED;
+    }
+
+    protected ExecHandle getExecHandle() {
+        if (execHandle == null) {
+            throw new IllegalStateException("you must call start() before calling this method");
+        }
+
+        return execHandle;
+    }
+
+    public ExecutionResult waitForFinish() {
+        return waitForStop(false);
+    }
+
+    public ExecutionFailure waitForFailure() {
+        return (ExecutionFailure) waitForStop(true);
+    }
+
+    protected ExecutionResult waitForStop(boolean expectFailure) {
+        ExecHandle execHandle = getExecHandle();
+        ExecResult execResult = execHandle.waitForFinish();
+        execResult.rethrowFailure(); // nop if all ok
+
+        String output = getStandardOutput();
+        String error = getErrorOutput();
+
+        boolean didFail = execResult.getExitValue() != 0;
+        if (didFail != expectFailure) {
+            String message = String.format("Gradle execution %s in %s with: %s %s%nOutput:%n%s%n-----%nError:%n%s%n-----%n",
+                    expectFailure ? "did not fail" : "failed", execHandle.getDirectory(), execHandle.getCommand(), execHandle.getArguments(), output, error);
+            throw new UnexpectedBuildFailure(message);
+        }
+        return expectFailure ? toExecutionFailure(output, error) : toExecutionResult(output, error);
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleBackedArtifactBuilder.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleBackedArtifactBuilder.java
new file mode 100644
index 0000000..576645a
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleBackedArtifactBuilder.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.executer;
+
+import org.apache.commons.io.FilenameUtils;
+import org.gradle.test.fixtures.file.TestFile;
+
+import java.io.File;
+
+public class GradleBackedArtifactBuilder implements ArtifactBuilder {
+    private final GradleExecuter executer;
+    private final TestFile rootDir;
+
+    public GradleBackedArtifactBuilder(GradleExecuter executer, File rootDir) {
+        this.executer = executer;
+        this.rootDir = new TestFile(rootDir);
+    }
+
+    public TestFile sourceFile(String path) {
+        return rootDir.file("src/main/java", path);
+    }
+
+    public TestFile resourceFile(String path) {
+        return rootDir.file("src/main/resources", path);
+    }
+
+    public void buildJar(File jarFile) {
+        rootDir.file("build.gradle").writelns(
+                "apply plugin: 'java'",
+                "dependencies { compile gradleApi() }",
+                String.format("jar.destinationDir = file('%s')", FilenameUtils.separatorsToUnix(jarFile.getParent())),
+                String.format("jar.archiveName = '%s'", jarFile.getName())
+        );
+        executer.inDirectory(rootDir).withTasks("clean", "jar").run();
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleContextualExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleContextualExecuter.java
new file mode 100644
index 0000000..8c0c50d
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleContextualExecuter.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures.executer;
+
+import org.gradle.test.fixtures.file.TestDirectoryProvider;
+
+/**
+ * Selects a different executer implementation based on the value of a system property.
+ *
+ * Facilitates running the same test in different execution modes.
+ */
+public class GradleContextualExecuter extends AbstractDelegatingGradleExecuter {
+
+    private static final String EXECUTER_SYS_PROP = "org.gradle.integtest.executer";
+    private static final String UNKNOWN_OS_SYS_PROP = "org.gradle.integtest.unknownos";
+
+    private Executer executerType;
+
+    private static enum Executer {
+        embedded(false),
+        forking(true),
+        daemon(true),
+        embeddedDaemon(false),
+        parallel(true, true);
+
+        final public boolean forks;
+        final public boolean executeParallel;
+
+        Executer(boolean forks) {
+            this(forks, false);
+        }
+
+        Executer(boolean forks, boolean parallel) {
+            this.forks = forks;
+            this.executeParallel = parallel;
+        }
+    }
+
+    private static Executer getSystemPropertyExecuter() {
+        return Executer.valueOf(System.getProperty(EXECUTER_SYS_PROP, Executer.forking.toString()));
+    }
+
+    public static boolean isEmbedded() {
+        return !getSystemPropertyExecuter().forks;
+    }
+
+    public static boolean isDaemon() {
+        return getSystemPropertyExecuter() == Executer.daemon;
+    }
+
+    public static boolean isParallel() {
+        return getSystemPropertyExecuter().executeParallel;
+    }
+
+    public GradleContextualExecuter(GradleDistribution distribution, TestDirectoryProvider testDirectoryProvider) {
+        super(distribution, testDirectoryProvider);
+        this.executerType = getSystemPropertyExecuter();
+    }
+
+    protected GradleExecuter configureExecuter() {
+        if (!getClass().desiredAssertionStatus()) {
+            throw new RuntimeException("Assertions must be enabled when running integration tests.");
+        }
+
+        GradleExecuter gradleExecuter = createExecuter(executerType);
+        configureExecuter(gradleExecuter);
+        try {
+            gradleExecuter.assertCanExecute();
+        } catch (AssertionError assertionError) {
+            gradleExecuter = new ForkingGradleExecuter(getDistribution(), getTestDirectoryProvider());
+            configureExecuter(gradleExecuter);
+        }
+
+        return gradleExecuter;
+    }
+
+    private void configureExecuter(GradleExecuter gradleExecuter) {
+        copyTo(gradleExecuter);
+
+        if (System.getProperty(UNKNOWN_OS_SYS_PROP) != null) {
+            gradleExecuter.withGradleOpts("-Dos.arch=unknown architecture", "-Dos.name=unknown operating system", "-Dos.version=unknown version");
+        }
+    }
+
+    private GradleExecuter createExecuter(Executer executerType) {
+        switch (executerType) {
+            case embeddedDaemon:
+                return new EmbeddedDaemonGradleExecuter(getDistribution(), getTestDirectoryProvider());
+            case embedded:
+                return new InProcessGradleExecuter(getDistribution(), getTestDirectoryProvider());
+            case daemon:
+                return new DaemonGradleExecuter(getDistribution(), getTestDirectoryProvider());
+            case parallel:
+                return new ParallelForkingGradleExecuter(getDistribution(), getTestDirectoryProvider());
+            case forking:
+                return new ForkingGradleExecuter(getDistribution(), getTestDirectoryProvider());
+            default:
+                throw new RuntimeException("Not a supported executer type: " + executerType);
+        }
+    }
+
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleDistribution.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleDistribution.java
new file mode 100644
index 0000000..55b0818
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleDistribution.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures.executer;
+
+import org.gradle.internal.jvm.Jvm;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.test.fixtures.file.TestDirectoryProvider;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.util.GradleVersion;
+
+public interface GradleDistribution {
+    /**
+     * Returns the root directory of the installed distribution
+     */
+    TestFile getGradleHomeDir();
+
+    /**
+     * Returns the binary distribution.
+     */
+    TestFile getBinDistribution();
+
+    /**
+     * Returns the version of this distribution.
+     */
+    GradleVersion getVersion();
+
+    /**
+     * Creates an executer which will use this distribution.
+     */
+    GradleExecuter executer(TestDirectoryProvider testDirectoryProvider);
+
+    /**
+     * Returns true if this distribution supports the given JVM.
+     */
+    boolean worksWith(Jvm jvm);
+
+    /**
+     * Returns true if this distribution supports the given Operating system.
+     */
+    boolean worksWith(OperatingSystem os);
+
+    /**
+     * Returns true if the daemon is supported by this distribution.
+     */
+    boolean isDaemonSupported();
+
+    /**
+     * Returns true if the configuring daemon idle timeout feature is supported by this distribution.
+     */
+    boolean isDaemonIdleTimeoutConfigurable();
+
+    /**
+     * Returns true if the tooling API is supported by this distribution.
+     */
+    boolean isToolingApiSupported();
+
+    /**
+     * Returns the version of the artifact cache layout
+     */
+    int getArtifactCacheLayoutVersion();
+
+    /**
+     * Returns true if the open API is supported by this distribution.
+     */
+    boolean isOpenApiSupported();
+
+    /**
+     * Returns true if the wrapper from this distribution can execute a build using the specified version.
+     * @param version
+     */
+    boolean wrapperCanExecute(GradleVersion version);
+
+    /**
+     * Early versions had bugs that prevented any values having spaces in them in GRADLE_OPTS or JAVA_OPTS.
+     *
+     * See http://issues.gradle.org/browse/GRADLE-1730
+     */
+    boolean isSupportsSpacesInGradleAndJavaOpts();
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleExecuter.java
new file mode 100644
index 0000000..66b1a01
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleExecuter.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures.executer;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.launcher.daemon.registry.DaemonRegistry;
+import org.gradle.test.fixtures.file.TestDirectoryProvider;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+public interface GradleExecuter {
+    /**
+     * Sets the working directory to use. Defaults to the test's temporary directory.
+     */
+    GradleExecuter inDirectory(File directory);
+
+    /**
+     * Enables search upwards. Defaults to false.
+     */
+    GradleExecuter withSearchUpwards();
+
+    /**
+     * Sets the task names to execute. Defaults to an empty list.
+     */
+    GradleExecuter withTasks(String... names);
+
+    /**
+     * Sets the task names to execute. Defaults to an empty list.
+     */
+    GradleExecuter withTasks(List<String> names);
+
+    GradleExecuter withTaskList();
+
+    GradleExecuter withDependencyList();
+
+    GradleExecuter withQuietLogging();
+
+    /**
+     * Sets the additional command-line arguments to use when executing the build. Defaults to an empty list.
+     */
+    GradleExecuter withArguments(String... args);
+
+    /**
+     * Sets the additional command-line arguments to use when executing the build. Defaults to an empty list.
+     */
+    GradleExecuter withArguments(List<String> args);
+
+    /**
+     * Adds an additional command-line argument to use when executing the build.
+     */
+    GradleExecuter withArgument(String arg);
+
+    /**
+     * Sets the environment variables to use when executing the build. Defaults to the environment of this process.
+     */
+    GradleExecuter withEnvironmentVars(Map<String, ?> environment);
+
+    GradleExecuter usingSettingsFile(File settingsFile);
+
+    GradleExecuter usingInitScript(File initScript);
+
+    /**
+     * Uses the given project directory
+     */
+    GradleExecuter usingProjectDirectory(File projectDir);
+
+    /**
+     * Uses the given build script
+     */
+    GradleExecuter usingBuildScript(File buildScript);
+
+    /**
+     * Sets the user's home dir to use when running the build. Implementations are not 100% accurate.
+     */
+    GradleExecuter withUserHomeDir(File userHomeDir);
+
+    /**
+     * Sets the <em>Gradle</em> user home dir. Setting to null requests that the executer use the real default Gradle user home dir rather than the
+     * default used for testing.
+     */
+    GradleExecuter withGradleUserHomeDir(File userHomeDir);
+
+    /**
+     * Sets the java home dir. Setting to null requests that the executer use the real default java home dir rather than the default used for testing.
+     */
+    GradleExecuter withJavaHome(File userHomeDir);
+
+    /**
+     * Sets the executable to use. Set to null to use the read default executable (if any) rather than the default used for testing.
+     */
+    GradleExecuter usingExecutable(String script);
+
+    /**
+     * Sets the stdin to use for the build. Defaults to an empty string.
+     */
+    GradleExecuter withStdIn(String text);
+
+    /**
+     * Sets the stdin to use for the build. Defaults to an empty string.
+     */
+    GradleExecuter withStdIn(InputStream stdin);
+
+    /**
+     * Specifies that the executer should not set any default jvm args.
+     */
+    GradleExecuter withNoDefaultJvmArgs();
+
+    /**
+     * Executes the requested build, asserting that the build succeeds. Resets the configuration of this executer.
+     *
+     * @return The result.
+     */
+    ExecutionResult run();
+
+    /**
+     * Executes the requested build, asserting that the build fails. Resets the configuration of this executer.
+     *
+     * @return The result.
+     */
+    ExecutionFailure runWithFailure();
+
+    /**
+     * Provides a daemon registry for any daemons started by this executer, which may be none.
+     *
+     * @return the daemon registry, never null.
+     */
+    DaemonRegistry getDaemonRegistry();
+
+    /**
+     * Starts executing the build asynchronously.
+     *
+     * @return the handle, never null.
+     */
+    GradleHandle start();
+
+    /**
+     * Adds options that should be used to start the JVM, if a JVM is to be started. Ignored if not.
+     *
+     * @param gradleOpts the jvm opts
+     *
+     * @return this executer
+     */
+    GradleExecuter withGradleOpts(String ... gradleOpts);
+
+    /**
+     * Sets the default character encoding to use.
+     *
+     * Only makes sense for forking executers.
+     *
+     * @return this executer
+     */
+    GradleExecuter withDefaultCharacterEncoding(String defaultCharacterEncoding);
+
+    /**
+     * Set the number of seconds an idle daemon should live for.
+     *
+     * @param secs
+     *
+     * @return this executer
+     */
+    GradleExecuter withDaemonIdleTimeoutSecs(int secs);
+
+    /**
+     * Set the working space for the daemon and launched daemons
+     *
+     * @param baseDir
+     *
+     * @return this executer
+     */
+    GradleExecuter withDaemonBaseDir(File baseDir);
+
+    /**
+     * Asserts that this executer will be able to run a build, given its current configuration.
+     *
+     * @throws AssertionError When this executer will not be able to run a build.
+     */
+    void assertCanExecute() throws AssertionError;
+
+    /**
+     * Adds an action to be called immediately before execution, to allow extra configuration to be injected.
+     */
+    void beforeExecute(Action<? super GradleExecuter> action);
+
+    /**
+     * Adds an action to be called immediately before execution, to allow extra configuration to be injected.
+     */
+    void beforeExecute(Closure action);
+
+    TestDirectoryProvider getTestDirectoryProvider();
+
+    GradleExecuter withDeprecationChecksDisabled();
+
+    GradleExecuter withStackTraceChecksDisabled();
+
+    GradleExecuter setAllowExtraLogging(boolean allowExtraLogging);
+
+    boolean isRequireGradleHome();
+
+    GradleExecuter requireGradleHome(boolean requireGradleHome);
+
+    GradleExecuter requireIsolatedDaemons();
+
+    GradleExecuter requireOwnGradleUserHomeDir();
+
+    File getGradleUserHomeDir();
+
+    GradleDistribution getDistribution();
+
+    /**
+     * Copies the settings from this executer to the given executer.
+     *
+     * @param executer The executer to copy to
+     * @return The passed in executer
+     */
+    GradleExecuter copyTo(GradleExecuter executer);
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleHandle.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleHandle.java
new file mode 100644
index 0000000..643c273
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleHandle.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures.executer;
+
+public interface GradleHandle {
+    String getStandardOutput();
+    String getErrorOutput();
+
+    GradleHandle abort();
+
+    ExecutionResult waitForFinish();
+    ExecutionFailure waitForFailure();
+
+    boolean isRunning();
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/InProcessGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/InProcessGradleExecuter.java
new file mode 100644
index 0000000..b4adf49
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/InProcessGradleExecuter.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.executer;
+
+import org.gradle.BuildListener;
+import org.gradle.BuildResult;
+import org.gradle.GradleLauncher;
+import org.gradle.StartParameter;
+import org.gradle.api.GradleException;
+import org.gradle.api.Task;
+import org.gradle.api.execution.TaskExecutionGraph;
+import org.gradle.api.execution.TaskExecutionGraphListener;
+import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.initialization.Settings;
+import org.gradle.api.internal.LocationAwareException;
+import org.gradle.api.internal.classpath.DefaultModuleRegistry;
+import org.gradle.api.internal.file.IdentityFileResolver;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.api.tasks.TaskState;
+import org.gradle.cli.CommandLineParser;
+import org.gradle.execution.MultipleBuildFailures;
+import org.gradle.initialization.DefaultCommandLineConverter;
+import org.gradle.initialization.DefaultGradleLauncherFactory;
+import org.gradle.internal.Factory;
+import org.gradle.internal.jvm.Jvm;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
+import org.gradle.internal.nativeplatform.services.NativeServices;
+import org.gradle.launcher.Main;
+import org.gradle.launcher.daemon.registry.DaemonRegistry;
+import org.gradle.process.internal.JavaExecHandleBuilder;
+import org.gradle.test.fixtures.file.TestDirectoryProvider;
+import org.gradle.util.DeprecationLogger;
+import org.hamcrest.Matcher;
+import org.hamcrest.Matchers;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.nio.charset.Charset;
+import java.util.*;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import static java.util.Arrays.asList;
+import static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+class InProcessGradleExecuter extends AbstractGradleExecuter {
+
+    private final ProcessEnvironment processEnvironment = NativeServices.getInstance().get(ProcessEnvironment.class);
+
+    InProcessGradleExecuter(GradleDistribution distribution, TestDirectoryProvider testDirectoryProvider) {
+        super(distribution, testDirectoryProvider);
+    }
+
+    @Override
+    public GradleExecuter reset() {
+        DeprecationLogger.reset();
+        return super.reset();
+    }
+
+    @Override
+    protected ExecutionResult doRun() {
+        OutputListenerImpl outputListener = new OutputListenerImpl();
+        OutputListenerImpl errorListener = new OutputListenerImpl();
+        BuildListenerImpl buildListener = new BuildListenerImpl();
+        BuildResult result = doRun(outputListener, errorListener, buildListener);
+        try {
+            result.rethrowFailure();
+        } catch (Exception e) {
+            throw new UnexpectedBuildFailure(e);
+        }
+        return new InProcessExecutionResult(buildListener.executedTasks, buildListener.skippedTasks,
+                outputListener.toString(), errorListener.toString());
+    }
+
+    @Override
+    protected ExecutionFailure doRunWithFailure() {
+        OutputListenerImpl outputListener = new OutputListenerImpl();
+        OutputListenerImpl errorListener = new OutputListenerImpl();
+        BuildListenerImpl buildListener = new BuildListenerImpl();
+        try {
+            doRun(outputListener, errorListener, buildListener).rethrowFailure();
+            throw new AssertionError("expected build to fail but it did not.");
+        } catch (GradleException e) {
+            return new InProcessExecutionFailure(buildListener.executedTasks, buildListener.skippedTasks,
+                    outputListener.writer.toString(), errorListener.writer.toString(), e);
+        }
+    }
+
+    @Override
+    protected GradleHandle doStart() {
+        return new ForkingGradleHandle(getDefaultCharacterEncoding(), new Factory<JavaExecHandleBuilder>() {
+            public JavaExecHandleBuilder create() {
+                JavaExecHandleBuilder builder = new JavaExecHandleBuilder(new IdentityFileResolver());
+                builder.workingDir(getWorkingDir());
+                Set<File> classpath = new DefaultModuleRegistry().getFullClasspath();
+                builder.classpath(classpath);
+                builder.setMain(Main.class.getName());
+                builder.args(getAllArgs());
+                return builder;
+            }
+        }).start();
+    }
+
+    private BuildResult doRun(final OutputListenerImpl outputListener, OutputListenerImpl errorListener, BuildListenerImpl listener) {
+        // Capture the current state of things that we will change during execution
+        InputStream originalStdIn = System.in;
+        Properties originalSysProperties = new Properties();
+        originalSysProperties.putAll(System.getProperties());
+        File originalUserDir = new File(originalSysProperties.getProperty("user.dir"));
+        Map<String, String> originalEnv = System.getenv();
+
+        // Augment the environment for the execution
+        System.setIn(getStdin());
+        processEnvironment.maybeSetProcessDir(getWorkingDir());
+        for (Map.Entry<String, String> entry : getEnvironmentVars().entrySet()) {
+            processEnvironment.maybeSetEnvironmentVariable(entry.getKey(), entry.getValue());
+        }
+        Map<String, String> implicitJvmSystemProperties = getImplicitJvmSystemProperties();
+        System.getProperties().putAll(implicitJvmSystemProperties);
+
+        StartParameter parameter = new StartParameter();
+        parameter.setLogLevel(LogLevel.INFO);
+        parameter.setSearchUpwards(true);
+        parameter.setCurrentDir(getWorkingDir());
+
+        CommandLineParser parser = new CommandLineParser();
+        DefaultCommandLineConverter converter = new DefaultCommandLineConverter();
+        converter.configure(parser);
+        converter.convert(parser.parse(getAllArgs()), parameter);
+
+        DefaultGradleLauncherFactory factory = (DefaultGradleLauncherFactory) GradleLauncher.getFactory();
+        factory.addListener(listener);
+        GradleLauncher gradleLauncher = GradleLauncher.newInstance(parameter);
+        gradleLauncher.addStandardOutputListener(outputListener);
+        gradleLauncher.addStandardErrorListener(errorListener);
+        try {
+            return gradleLauncher.run();
+        } finally {
+            // Restore the environment
+            System.setProperties(originalSysProperties);
+            processEnvironment.maybeSetProcessDir(originalUserDir);
+            for (Map.Entry<String, String> entry : originalEnv.entrySet()) {
+                String oldValue = entry.getValue();
+                if (oldValue != null) {
+                    processEnvironment.maybeSetEnvironmentVariable(entry.getKey(), oldValue);
+                } else {
+                    processEnvironment.maybeRemoveEnvironmentVariable(entry.getKey());
+                }
+            }
+            factory.removeListener(listener);
+            System.setIn(originalStdIn);
+        }
+    }
+
+    public DaemonRegistry getDaemonRegistry() {
+        throw new UnsupportedOperationException();
+    }
+
+    public void assertCanExecute() {
+        assertNull(getExecutable());
+        assertEquals(getJavaHome(), Jvm.current().getJavaHome());
+        assertEquals(getDefaultCharacterEncoding(), Charset.defaultCharset().name());
+        assertFalse(isRequireGradleHome());
+    }
+
+    private static class BuildListenerImpl implements TaskExecutionGraphListener, BuildListener {
+        private final List<String> executedTasks = new CopyOnWriteArrayList<String>();
+        private final Set<String> skippedTasks = new CopyOnWriteArraySet<String>();
+
+        public void graphPopulated(TaskExecutionGraph graph) {
+            List<Task> planned = new ArrayList<Task>(graph.getAllTasks());
+            graph.addTaskExecutionListener(new TaskListenerImpl(planned, executedTasks, skippedTasks));
+        }
+
+        public void buildStarted(Gradle gradle) {
+            DeprecationLogger.setLogTrace(true);
+        }
+
+        public void settingsEvaluated(Settings settings) {
+
+        }
+
+        public void projectsLoaded(Gradle gradle) {
+
+        }
+
+        public void projectsEvaluated(Gradle gradle) {
+
+        }
+
+        public void buildFinished(BuildResult result) {
+            DeprecationLogger.setLogTrace(false);
+        }
+    }
+
+    private static class OutputListenerImpl implements StandardOutputListener {
+        private StringWriter writer = new StringWriter();
+
+        @Override
+        public String toString() {
+            return writer.toString();
+        }
+
+        public void onOutput(CharSequence output) {
+            writer.append(output);
+        }
+    }
+
+    private static class TaskListenerImpl implements TaskExecutionListener {
+        private final List<Task> planned;
+        private final List<String> executedTasks;
+        private final Set<String> skippedTasks;
+
+        public TaskListenerImpl(List<Task> planned, List<String> executedTasks, Set<String> skippedTasks) {
+            this.planned = planned;
+            this.executedTasks = executedTasks;
+            this.skippedTasks = skippedTasks;
+        }
+
+        public void beforeExecute(Task task) {
+            assertTrue(planned.contains(task));
+
+            String taskPath = path(task);
+            if (taskPath.startsWith(":buildSrc:")) {
+                return;
+            }
+
+            executedTasks.add(taskPath);
+        }
+
+        public void afterExecute(Task task, TaskState state) {
+            String taskPath = path(task);
+            if (taskPath.startsWith(":buildSrc:")) {
+                return;
+            }
+
+            if (state.getSkipped()) {
+                skippedTasks.add(taskPath);
+            }
+        }
+
+        private String path(Task task) {
+            return task.getProject().getGradle().getParent() == null ? task.getPath() : ":" + task.getProject().getRootProject().getName() + task.getPath();
+        }
+    }
+
+    public static class InProcessExecutionResult implements ExecutionResult {
+        private final List<String> plannedTasks;
+        private final Set<String> skippedTasks;
+        private final String output;
+        private final String error;
+
+        public InProcessExecutionResult(List<String> plannedTasks, Set<String> skippedTasks, String output,
+                                        String error) {
+            this.plannedTasks = plannedTasks;
+            this.skippedTasks = skippedTasks;
+            this.output = output;
+            this.error = error;
+        }
+
+        public String getOutput() {
+            return output;
+        }
+
+        public ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines) {
+            new SequentialOutputMatcher().assertOutputMatches(expectedOutput, getOutput(), ignoreExtraLines);
+            return this;
+        }
+
+        public String getError() {
+            return error;
+        }
+
+        public List<String> getExecutedTasks() {
+            return new ArrayList<String>(plannedTasks);
+        }
+
+        public ExecutionResult assertTasksExecuted(String... taskPaths) {
+            List<String> expected = Arrays.asList(taskPaths);
+            assertThat(plannedTasks, equalTo(expected));
+            return this;
+        }
+
+        public ExecutionResult assertProjectsEvaluated(String... projectPaths) {
+            new OutputScraper(getOutput()).assertProjectsEvaluated(asList(projectPaths));
+            return this;
+        }
+
+        public Set<String> getSkippedTasks() {
+            return new HashSet<String>(skippedTasks);
+        }
+
+        public ExecutionResult assertTasksSkipped(String... taskPaths) {
+            Set<String> expected = new HashSet<String>(Arrays.asList(taskPaths));
+            assertThat(skippedTasks, equalTo(expected));
+            return this;
+        }
+
+        public ExecutionResult assertTaskSkipped(String taskPath) {
+            assertThat(skippedTasks, hasItem(taskPath));
+            return this;
+        }
+
+        public ExecutionResult assertTasksNotSkipped(String... taskPaths) {
+            Set<String> expected = new HashSet<String>(Arrays.asList(taskPaths));
+            Set<String> notSkipped = getNotSkippedTasks();
+            assertThat(notSkipped, equalTo(expected));
+            return this;
+        }
+
+        public ExecutionResult assertTaskNotSkipped(String taskPath) {
+            assertThat(getNotSkippedTasks(), hasItem(taskPath));
+            return this;
+        }
+
+        private Set<String> getNotSkippedTasks() {
+            Set<String> notSkipped = new HashSet<String>(plannedTasks);
+            notSkipped.removeAll(skippedTasks);
+            return notSkipped;
+        }
+    }
+
+    private static class InProcessExecutionFailure extends InProcessExecutionResult implements ExecutionFailure {
+        private final GradleException failure;
+
+        public InProcessExecutionFailure(List<String> tasks, Set<String> skippedTasks, String output, String error,
+                                         GradleException failure) {
+            super(tasks, skippedTasks, output, error);
+            this.failure = failure;
+        }
+
+        public ExecutionFailure assertHasLineNumber(int lineNumber) {
+            assertThat(failure.getMessage(), containsString(String.format(" line: %d", lineNumber)));
+            return this;
+
+        }
+
+        public ExecutionFailure assertHasFileName(String filename) {
+            assertThat(failure.getMessage(), startsWith(String.format("%s", filename)));
+            return this;
+        }
+
+        public ExecutionFailure assertHasCause(String description) {
+            assertThatCause(startsWith(description));
+            return this;
+        }
+
+        public ExecutionFailure assertThatCause(final Matcher<String> matcher) {
+            List<Throwable> causes = new ArrayList<Throwable>();
+            extractCauses(failure, causes);
+            assertThat(causes, Matchers.<Throwable>hasItem(hasMessage(matcher)));
+            return this;
+        }
+
+        private void extractCauses(Throwable failure, List<Throwable> causes) {
+            if (failure instanceof MultipleBuildFailures) {
+                MultipleBuildFailures exception = (MultipleBuildFailures) failure;
+                for (Throwable componentFailure : exception.getCauses()) {
+                    extractCauses(componentFailure, causes);
+                }
+            } else if (failure instanceof LocationAwareException) {
+                causes.addAll(((LocationAwareException) failure).getReportableCauses());
+            } else {
+                causes.add(failure.getCause());
+            }
+        }
+
+        public ExecutionFailure assertHasNoCause() {
+            if (failure instanceof LocationAwareException) {
+                LocationAwareException exception = (LocationAwareException) failure;
+                assertThat(exception.getReportableCauses(), isEmpty());
+            } else {
+                assertThat(failure.getCause(), nullValue());
+            }
+            return this;
+        }
+
+        public ExecutionFailure assertHasDescription(String context) {
+            assertThatDescription(startsWith(context));
+            return this;
+        }
+
+        public ExecutionFailure assertThatDescription(Matcher<String> matcher) {
+            assertThat(failure.getMessage(), containsLine(matcher));
+            return this;
+        }
+
+        public ExecutionFailure assertTestsFailed() {
+            new DetailedExecutionFailure(this).assertTestsFailed();
+            return this;
+        }
+
+        public DependencyResolutionFailure getDependencyResolutionFailure() {
+            return new DependencyResolutionFailure(this);
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/IntegrationTestBuildContext.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/IntegrationTestBuildContext.java
new file mode 100644
index 0000000..dec09f1
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/IntegrationTestBuildContext.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.executer;
+
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.util.GradleVersion;
+
+import java.io.File;
+
+/**
+ * Provides values that are set during the build, or defaulted when not running in a build context (e.g. IDE).
+ */
+public class IntegrationTestBuildContext {
+
+    public TestFile getGradleHomeDir() {
+        return file("integTest.gradleHomeDir", null);
+    }
+
+    public TestFile getSamplesDir() {
+        return file("integTest.samplesdir", String.format("%s/samples", getGradleHomeDir()));
+    }
+
+    public TestFile getUserGuideOutputDir() {
+        return file("integTest.userGuideOutputDir", "subprojects/docs/src/samples/userguideOutput");
+    }
+    
+    public TestFile getUserGuideInfoDir() {
+        return file("integTest.userGuideInfoDir", "subprojects/docs/build/src");    
+    }
+    
+    public TestFile getDistributionsDir() {
+        return file("integTest.distsDir", "build/distributions");
+    }
+    
+    public TestFile getLibsRepo() {
+        return file("integTest.libsRepo", "build/repo");
+    }
+
+    public TestFile getDaemonBaseDir() {
+        return file("org.gradle.integtest.daemon.registry", "build/daemon");
+    }
+
+    public TestFile getGradleUserHomeDir() {
+        return file("integTest.gradleUserHomeDir", "intTestHomeDir").file("worker-1");
+    }
+
+    public GradleVersion getVersion() {
+        return GradleVersion.current();
+    }
+
+    public GradleDistribution distribution(String version) {
+        if (version.equals(getVersion().getVersion())) {
+            return new UnderDevelopmentGradleDistribution();
+        }
+        TestFile previousVersionDir = getGradleUserHomeDir().getParentFile().file("previousVersion");
+        return new ReleasedGradleDistribution(version, previousVersionDir.file(version));
+    }
+
+    private static TestFile file(String propertyName, String defaultFile) {
+        String path = System.getProperty(propertyName, defaultFile);
+        if (path == null) {
+            throw new RuntimeException(String.format("You must set the '%s' property to run the integration tests. The default passed was: '%s'",
+                    propertyName, defaultFile));
+        }
+        return new TestFile(new File(path));
+    }
+
+
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScraper.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScraper.groovy
new file mode 100644
index 0000000..b79cc23
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScraper.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.executer
+
+/**
+ * by Szczepan Faber, created at: 12/21/12
+ */
+class OutputScraper {
+    List<String> evaluatedProjects
+    final static String EVAL_NOT_FOUND = "No evaluated projects found. Make sure you run the build with '-i'."
+    final static String ALL_EVAL_ERROR = "The gradle.projectsEvaluated hook must not be fired before all projects have been evaluated."
+
+    OutputScraper(String output) {
+        this(getEvaluatedProjects(output))
+    }
+    OutputScraper(List<String> evaluatedProjects) {
+        this.evaluatedProjects = evaluatedProjects
+    }
+
+    void assertProjectsEvaluated(Collection expectedProjects) {
+        if (!expectedProjects.empty && evaluatedProjects.empty) {
+            throw new AssertionError(EVAL_NOT_FOUND)
+        }
+        assert evaluatedProjects == expectedProjects as List
+    }
+
+    static List<String> getEvaluatedProjects(String output) {
+        def evaluatedProjects = []
+        def allEvaluated = false
+        output.eachLine {
+            if (it.startsWith('All projects evaluated.')) {
+                allEvaluated = true
+            } else if (it.startsWith('Evaluating root project')) {
+                assertNotEvaluated(allEvaluated, it)
+                evaluatedProjects << ':'
+            } else {
+                def m = it =~ /Evaluating project '(.*)' using.*/
+                if (m) {
+                    assertNotEvaluated(allEvaluated, it)
+                    evaluatedProjects << m.group(1)
+                }
+            }
+        }
+        evaluatedProjects
+    }
+
+    final static void assertNotEvaluated(boolean allEvaluated, String line) {
+        assert !allEvaluated : ALL_EVAL_ERROR + " Found this line after evaluation of all projects: $line"
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailure.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailure.java
new file mode 100644
index 0000000..3a22dce
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailure.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures.executer;
+
+import org.hamcrest.Matcher;
+
+import java.util.regex.Pattern;
+
+import static org.hamcrest.Matchers.not;
+import static org.gradle.util.Matchers.containsLine;
+import static org.gradle.util.Matchers.matchesRegexp;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+public class OutputScrapingExecutionFailure extends OutputScrapingExecutionResult implements ExecutionFailure {
+    private final Pattern causePattern = Pattern.compile("(?m)\\s*> ");
+
+    public OutputScrapingExecutionFailure(String output, String error) {
+        super(output, error);
+    }
+
+    public ExecutionFailure assertHasLineNumber(int lineNumber) {
+        assertThat(getError(), containsString(String.format(" line: %d", lineNumber)));
+        return this;
+    }
+
+    public ExecutionFailure assertHasFileName(String filename) {
+        assertThat(getError(), containsLine(startsWith(filename)));
+        return this;
+    }
+
+    public ExecutionFailure assertHasCause(String description) {
+        assertThatCause(startsWith(description));
+        return this;
+    }
+
+    public ExecutionFailure assertThatCause(Matcher<String> matcher) {
+        String error = getError();
+        java.util.regex.Matcher regExpMatcher = causePattern.matcher(error);
+        int pos = 0;
+        while (pos < error.length()) {
+            if (!regExpMatcher.find(pos)) {
+                break;
+            }
+            int start = regExpMatcher.end();
+            String cause;
+            if (regExpMatcher.find(start)) {
+                cause = error.substring(start, regExpMatcher.start());
+                pos = regExpMatcher.start();
+            } else {
+                cause = error.substring(start);
+                pos = error.length();
+            }
+            if (matcher.matches(cause)) {
+                return this;
+            }
+        }
+        fail(String.format("No matching cause found in '%s'", error));
+        return this;
+    }
+
+    public ExecutionFailure assertHasNoCause() {
+        assertThat(getError(), not(matchesRegexp(causePattern)));
+        return this;
+    }
+
+    public ExecutionFailure assertHasDescription(String context) {
+        assertThatDescription(startsWith(context));
+        return this;
+    }
+
+    public ExecutionFailure assertThatDescription(Matcher<String> matcher) {
+        assertThat(getError(), containsLine(matcher));
+        return this;
+    }
+
+    public ExecutionFailure assertTestsFailed() {
+        new DetailedExecutionFailure(this).assertTestsFailed();
+        return this;
+    }
+
+    public DependencyResolutionFailure getDependencyResolutionFailure() {
+        return new DependencyResolutionFailure(this);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionResult.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionResult.java
new file mode 100644
index 0000000..4a462f4
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionResult.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures.executer;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.gradle.api.Action;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.*;
+import java.util.regex.Pattern;
+
+import static java.util.Arrays.asList;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasItem;
+import static org.junit.Assert.assertThat;
+
+public class OutputScrapingExecutionResult implements ExecutionResult {
+    private final String output;
+    private final String error;
+
+    //for example: ':a SKIPPED' or ':foo:bar:baz UP-TO-DATE' but not ':a'
+    private final Pattern skippedTaskPattern = Pattern.compile("(:\\S+?(:\\S+?)*)\\s+((SKIPPED)|(UP-TO-DATE))");
+
+    //for example: ':hey' or ':a SKIPPED' or ':foo:bar:baz UP-TO-DATE' but not ':a FOO'
+    private final Pattern taskPattern = Pattern.compile("(:\\S+?(:\\S+?)*)((\\s+SKIPPED)|(\\s+UP-TO-DATE)|(\\s*))");
+
+    public OutputScrapingExecutionResult(String output, String error) {
+        this.output = output;
+        this.error = error;
+    }
+
+    public String getOutput() {
+        return output;
+    }
+
+    public ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines) {
+        new SequentialOutputMatcher().assertOutputMatches(expectedOutput, getOutput(), ignoreExtraLines);
+        return this;
+    }
+
+    public String getError() {
+        return error;
+    }
+
+    public List<String> getExecutedTasks() {
+        return grepTasks(taskPattern);
+    }
+
+    public ExecutionResult assertTasksExecuted(String... taskPaths) {
+        List<String> expectedTasks = Arrays.asList(taskPaths);
+        assertThat(String.format("Expected tasks %s not found in process output:%n%s", expectedTasks, getOutput()), getExecutedTasks(), equalTo(expectedTasks));
+        return this;
+    }
+
+    public ExecutionResult assertProjectsEvaluated(String... projectPaths) {
+        new OutputScraper(getOutput()).assertProjectsEvaluated(asList(projectPaths));
+        return this;
+    }
+
+    public Set<String> getSkippedTasks() {
+        return new HashSet<String>(grepTasks(skippedTaskPattern));
+    }
+
+    public ExecutionResult assertTasksSkipped(String... taskPaths) {
+        Set<String> expectedTasks = new HashSet<String>(Arrays.asList(taskPaths));
+        assertThat(String.format("Expected skipped tasks %s not found in process output:%n%s", expectedTasks, getOutput()), getSkippedTasks(), equalTo(expectedTasks));
+        return this;
+    }
+
+    public ExecutionResult assertTaskSkipped(String taskPath) {
+        Set<String> tasks = new HashSet<String>(getSkippedTasks());
+        assertThat(String.format("Expected skipped task %s not found in process output:%n%s", taskPath, getOutput()), tasks, hasItem(taskPath));
+        return this;
+    }
+
+    public ExecutionResult assertTasksNotSkipped(String... taskPaths) {
+        Set<String> tasks = new HashSet<String>(getNotSkippedTasks());
+        Set<String> expectedTasks = new HashSet<String>(Arrays.asList(taskPaths));
+        assertThat(String.format("Expected executed tasks %s not found in process output:%n%s", expectedTasks, getOutput()), tasks, equalTo(expectedTasks));
+        return this;
+    }
+
+    private Collection<String> getNotSkippedTasks() {
+        List all = getExecutedTasks();
+        Set skipped = getSkippedTasks();
+        return CollectionUtils.subtract(all, skipped);
+    }
+
+    public ExecutionResult assertTaskNotSkipped(String taskPath) {
+        Set<String> tasks = new HashSet<String>(getNotSkippedTasks());
+        assertThat(String.format("Expected executed task %s not found in process output:%n%s", taskPath, getOutput()), tasks, hasItem(taskPath));
+        return this;
+    }
+
+    private List<String> grepTasks(final Pattern pattern) {
+        final LinkedList<String> tasks = new LinkedList<String>();
+
+        eachLine(new Action<String>() {
+            public void execute(String s) {
+                java.util.regex.Matcher matcher = pattern.matcher(s);
+                if (matcher.matches()) {
+                    String taskName = matcher.group(1);
+                    if (!taskName.startsWith(":buildSrc:")) {
+                        //for INFO/DEBUG level the task may appear twice - once for the execution, once for the UP-TO-DATE
+                        //so I'm not adding the task to the list if it is the same as previously added task.
+                        if (tasks.size() == 0 || !tasks.getLast().equals(taskName)) {
+                            tasks.add(taskName);
+                        }
+                    }
+                }
+            }
+        });
+
+        return tasks;
+    }
+
+    private void eachLine(Action<String> action) {
+        BufferedReader reader = new BufferedReader(new StringReader(getOutput()));
+        String line;
+        try {
+            while ((line = reader.readLine()) != null) {
+                action.execute(line);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingGradleHandle.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingGradleHandle.java
new file mode 100644
index 0000000..b869ed8
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingGradleHandle.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures.executer;
+
+abstract public class OutputScrapingGradleHandle implements GradleHandle {
+
+    protected ExecutionResult toExecutionResult(String output, String error) {
+        return new OutputScrapingExecutionResult(output, error);
+    }
+
+    protected ExecutionResult toExecutionFailure(String output, String error) {
+        return new OutputScrapingExecutionFailure(output, error);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelForkingGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelForkingGradleExecuter.java
new file mode 100644
index 0000000..7ab0329
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelForkingGradleExecuter.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.executer;
+
+import org.gradle.internal.Factory;
+import org.gradle.process.internal.ExecHandleBuilder;
+import org.gradle.test.fixtures.file.TestDirectoryProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class ParallelForkingGradleExecuter extends ForkingGradleExecuter {
+    public ParallelForkingGradleExecuter(GradleDistribution distribution, TestDirectoryProvider testDirectoryProvider) {
+        super(distribution, testDirectoryProvider);
+    }
+
+    @Override
+    protected List<String> getAllArgs() {
+        List<String> args = new ArrayList<String>();
+        args.addAll(super.getAllArgs());
+        args.add("--parallel-threads=4");
+        return args;
+    }
+
+    @Override
+    protected ForkingGradleHandle createGradleHandle(String encoding, Factory<ExecHandleBuilder> execHandleFactory) {
+        return new ParallelForkingGradleHandle(encoding, execHandleFactory);
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelForkingGradleHandle.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelForkingGradleHandle.java
new file mode 100644
index 0000000..dfc05ed
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelForkingGradleHandle.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.executer;
+
+import org.gradle.internal.Factory;
+import org.gradle.process.internal.AbstractExecHandleBuilder;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+public class ParallelForkingGradleHandle extends ForkingGradleHandle {
+
+    public ParallelForkingGradleHandle(String outputEncoding, Factory<? extends AbstractExecHandleBuilder> execHandleFactory) {
+        super(outputEncoding, execHandleFactory);
+    }
+
+    @Override
+    protected ExecutionResult toExecutionResult(String output, String error) {
+        return new ParallelExecutionResult(output, error);
+    }
+
+    @Override
+    protected ExecutionResult toExecutionFailure(String output, String error) {
+        return new ParallelExecutionResult(output, error);
+    }
+
+    /**
+     * Need a different output comparator for parallel execution.
+     */
+    private static class ParallelExecutionResult extends OutputScrapingExecutionFailure {
+        public ParallelExecutionResult(String output, String error) {
+            super(output, error);
+        }
+
+        @Override
+        public ExecutionResult assertTasksExecuted(String... taskPaths) {
+            Set<String> expectedTasks = new HashSet<String>(Arrays.asList(taskPaths));
+            assertThat(String.format("Expected tasks %s not found in process output:%n%s", expectedTasks, getOutput()), new HashSet<String>(getExecutedTasks()), equalTo(expectedTasks));
+            return this;
+        }
+
+        @Override
+        public String getOutput() {
+            String output = super.getOutput();
+            String parallelWarningPrefix = "Parallel project execution is an \"incubating\" feature";
+            if (output.startsWith(parallelWarningPrefix)) {
+                return output.replaceFirst(String.format("(?m)%s.+$\n", parallelWarningPrefix), "");
+            }
+            return output;
+        }
+
+        @Override
+        public ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines) {
+            new ParallelOutputMatcher().assertOutputMatches(expectedOutput, getOutput(), ignoreExtraLines);
+            return this;
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelOutputMatcher.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelOutputMatcher.groovy
new file mode 100644
index 0000000..4b06e58
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelOutputMatcher.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.integtests.fixtures.executer
+
+import org.junit.Assert
+import org.gradle.internal.SystemProperties
+
+/**
+ * Checks that all lines contained in the expected output are present in the actual output, in any order.
+ */
+class ParallelOutputMatcher extends SequentialOutputMatcher {
+    private static final String NL = SystemProperties.lineSeparator
+
+    protected void assertOutputLinesMatch(List<String> expectedLines, List<String> actualLines, boolean ignoreExtraLines, String actual) {
+        List<String> unmatchedLines = new ArrayList<String>(actualLines)
+        expectedLines.removeAll('')
+        unmatchedLines.removeAll('')
+
+        expectedLines.each { expectedLine ->
+            def matchedLine = unmatchedLines.find { actualLine ->
+                compare(expectedLine, actualLine)
+            }
+            if (matchedLine) {
+                unmatchedLines.remove(matchedLine)
+            } else {
+                Assert.fail("Line missing from output.${NL}${expectedLine}${NL}---${NL}Actual output:${NL}$actual${NL}---")
+            }
+        }
+
+        if (!(ignoreExtraLines || unmatchedLines.empty)) {
+            def unmatched = unmatchedLines.join(NL)
+            Assert.fail("Extra lines in output.${NL}${unmatched}${NL}---${NL}Actual output:${NL}$actual${NL}---")
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ProgressLoggingFixture.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ProgressLoggingFixture.groovy
new file mode 100644
index 0000000..02765ab
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ProgressLoggingFixture.groovy
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.executer
+
+import org.gradle.test.fixtures.file.TestDirectoryProvider
+import org.gradle.test.fixtures.file.TestDirectoryProviderFinder
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.RuleHelper
+import org.junit.rules.MethodRule
+import org.junit.runners.model.FrameworkMethod
+import org.junit.runners.model.Statement
+
+class ProgressLoggingFixture implements MethodRule {
+
+    private TestFile loggingOutputFile = null
+
+    boolean downloadProgressLogged(String url) {
+        return progressLogged("Download", url)
+    }
+
+    boolean uploadProgressLogged(String url) {
+        return progressLogged("Upload", url)
+    }
+
+    private boolean progressLogged(String operation, String url) {
+        def lines = loggingOutputFile.exists() ? loggingOutputFile.text.readLines() : []
+        def startIndex = lines.indexOf("[START " + operation + " " + url + "]")
+        if (startIndex == -1) {
+            return false
+        }
+        lines = lines[startIndex..<lines.size()]
+        lines = lines[0..lines.indexOf("[END " + operation + " " + url + "]")]
+        lines.size() >= 2
+    }
+
+    Statement apply(Statement base, FrameworkMethod method, Object target) {
+        TestFile initFile
+        GradleExecuter executer = RuleHelper.getField(target, GradleExecuter)
+        TestDirectoryProvider testDirectoryProvider = new TestDirectoryProviderFinder().findFor(target)
+        TestFile temporaryFolder = testDirectoryProvider.testDirectory
+        loggingOutputFile = temporaryFolder.file("loggingoutput.log")
+        initFile = temporaryFolder.file("progress-logging-init.gradle")
+        initFile.text = """import org.gradle.logging.internal.*
+                           File outputFile = file("${loggingOutputFile.toURI()}")
+                           OutputEventListener outputEventListener = new OutputEventListener() {
+                                void onOutput(OutputEvent event) {
+                                    if (event instanceof ProgressStartEvent) {
+                                        outputFile << "[START \$event.description]\\n"
+                                    } else if (event instanceof ProgressEvent) {
+                                        outputFile << "[\$event.status]\\n"
+                                    } else if (event instanceof ProgressCompleteEvent) {
+                                        outputFile << "[END \$event.description]\\n"
+                                    }
+                                }
+                           }
+                           def loggingOutputInternal = gradle.services.get(LoggingOutputInternal)
+                           loggingOutputInternal.addOutputEventListener(outputEventListener)
+                           buildFinished{
+                                loggingOutputInternal.removeOutputEventListener(outputEventListener)
+                           }"""
+        executer.beforeExecute {
+            usingInitScript(initFile)
+        }
+
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                base.evaluate();
+            }
+        };
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/RedirectMavenCentral.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/RedirectMavenCentral.groovy
new file mode 100644
index 0000000..7a01edf
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/RedirectMavenCentral.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures.executer
+
+import org.gradle.api.Action
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.test.fixtures.file.TestDirectoryProvider
+
+class RedirectMavenCentral implements Action<GradleExecuter> {
+    private final TestDirectoryProvider testWorkDirProvider
+
+    RedirectMavenCentral(TestDirectoryProvider testWorkDirProvider) {
+        this.testWorkDirProvider = testWorkDirProvider
+    }
+
+    void execute(GradleExecuter executer) {
+        if (OperatingSystem.current().isWindows()) {
+            return
+        }
+
+        def file = testWorkDirProvider.testDirectory.createFile("redirect-maven-central-init.gradle")
+        file.text = """
+allprojects {
+    repositories.withType(org.gradle.api.artifacts.repositories.MavenArtifactRepository) {
+        if (url == new URI('http://repo1.maven.org/maven2/')) {
+            url = "http://repo.gradle.org/gradle/repo1"
+        }
+    }
+}
+"""
+        executer.withArgument("-I$file.absolutePath")
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ReleasedGradleDistribution.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ReleasedGradleDistribution.groovy
new file mode 100644
index 0000000..fe1666e
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ReleasedGradleDistribution.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures.executer
+
+import org.gradle.CacheUsage
+import org.gradle.api.Action
+import org.gradle.cache.PersistentCache
+import org.gradle.cache.internal.CacheFactory
+import org.gradle.cache.internal.DefaultCacheFactory
+import org.gradle.cache.internal.DefaultFileLockManager
+import org.gradle.cache.internal.DefaultProcessMetaDataProvider
+import org.gradle.cache.internal.FileLockManager.LockMode
+import org.gradle.internal.nativeplatform.ProcessEnvironment
+import org.gradle.internal.nativeplatform.services.NativeServices
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.DistributionLocator
+import org.gradle.util.GradleVersion
+
+class ReleasedGradleDistribution extends DefaultGradleDistribution {
+
+    private static final CACHE_FACTORY = createCacheFactory()
+
+    private static CacheFactory createCacheFactory() {
+        return new DefaultCacheFactory(
+                new DefaultFileLockManager(
+                        new DefaultProcessMetaDataProvider(
+                                NativeServices.getInstance().get(ProcessEnvironment)),
+                        20 * 60 * 1000 // allow up to 20 minutes to download a distribution
+                )).create()
+    }
+
+    private final TestFile versionDir
+    private PersistentCache cache
+
+    ReleasedGradleDistribution(String version, TestFile versionDir) {
+        super(GradleVersion.version(version), versionDir.file("gradle-$version"), versionDir.file("gradle-$version-bin.zip"))
+        this.versionDir = versionDir
+    }
+
+    TestFile getBinDistribution() {
+        download()
+        super.getBinDistribution()
+    }
+
+    def TestFile getGradleHomeDir() {
+        download()
+        super.getGradleHomeDir()
+    }
+
+    private void download() {
+        if (cache == null) {
+            def downloadAction = { cache ->
+                URL url = new DistributionLocator().getDistributionFor(getVersion()).toURL()
+                System.out.println("downloading $url")
+                super.binDistribution.copyFrom(url)
+                super.binDistribution.usingNativeTools().unzipTo(versionDir)
+            }
+            //noinspection GrDeprecatedAPIUsage
+            cache = CACHE_FACTORY.open(versionDir, version.version, CacheUsage.ON, null, [:], LockMode.Shared, downloadAction as Action)
+        }
+
+        super.binDistribution.assertIsFile()
+        super.gradleHomeDir.assertIsDir()
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/SequentialOutputMatcher.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/SequentialOutputMatcher.groovy
new file mode 100644
index 0000000..af77303
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/SequentialOutputMatcher.groovy
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.integtests.fixtures.executer
+
+import org.gradle.internal.SystemProperties
+import org.junit.Assert
+import org.gradle.util.TextUtil
+
+/**
+ * Check that the actual output lines match the expected output lines in content and order.
+ */
+class SequentialOutputMatcher {
+    private static final String NL = SystemProperties.lineSeparator
+
+    public void assertOutputMatches(String expected, String actual, boolean ignoreExtraLines) {
+        List actualLines = normaliseOutput(actual.readLines())
+        List expectedLines = expected.readLines()
+        assertOutputLinesMatch(expectedLines, actualLines, ignoreExtraLines, actual)
+    }
+
+    protected void assertOutputLinesMatch(List<String> expectedLines, List<String> actualLines, boolean ignoreExtraLines, String actual) {
+        int pos = 0
+        for (; pos < actualLines.size() && pos < expectedLines.size(); pos++) {
+            String expectedLine = expectedLines[pos]
+            String actualLine = actualLines[pos]
+            boolean matches = compare(expectedLine, actualLine)
+            if (!matches) {
+                if (expectedLine.contains(actualLine)) {
+                    Assert.fail("Missing text at line ${pos + 1}.${NL}Expected: ${expectedLine}${NL}Actual: ${actualLine}${NL}---${NL}Actual output:${NL}$actual${NL}---")
+                }
+                if (actualLine.contains(expectedLine)) {
+                    Assert.fail("Extra text at line ${pos + 1}.${NL}Expected: ${expectedLine}${NL}Actual: ${actualLine}${NL}---${NL}Actual output:${NL}$actual${NL}---")
+                }
+                Assert.fail("Unexpected value at line ${pos + 1}.${NL}Expected: ${expectedLine}${NL}Actual: ${actualLine}${NL}---${NL}Actual output:${NL}$actual${NL}---")
+            }
+        }
+        if (pos == actualLines.size() && pos < expectedLines.size()) {
+            Assert.fail("Lines missing from actual result, starting at line ${pos + 1}.${NL}Expected: ${expectedLines[pos]}${NL}Actual output:${NL}$actual${NL}---")
+        }
+        if (!ignoreExtraLines && pos < actualLines.size() && pos == expectedLines.size()) {
+            Assert.fail("Extra lines in actual result, starting at line ${pos + 1}.${NL}Actual: ${actualLines[pos]}${NL}Actual output:${NL}$actual${NL}---")
+        }
+    }
+
+    private List<String> normaliseOutput(List<String> lines) {
+        if (lines.empty) {
+            return lines;
+        }
+        List<String> result = new ArrayList<String>()
+        for (String line : lines) {
+            if (line.matches('Download .+')) {
+                // ignore
+            } else {
+                result << line
+            }
+        }
+        return result
+    }
+
+    protected boolean compare(String expected, String actual) {
+        if (actual == expected) {
+            return true
+        }
+
+        if (expected == 'Total time: 1 secs') {
+            return actual.matches('Total time: .+ secs')
+        }
+
+        // Normalise default object toString() values
+        actual = actual.replaceAll('(\\w+(\\.\\w+)*)@\\p{XDigit}+', '$1 at 12345')
+        // Normalise file separators
+        actual = TextUtil.normaliseFileSeparators(actual)
+
+        return actual == expected
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/UnderDevelopmentGradleDistribution.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/UnderDevelopmentGradleDistribution.java
new file mode 100644
index 0000000..3767723
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/UnderDevelopmentGradleDistribution.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.executer;
+
+public class UnderDevelopmentGradleDistribution extends DefaultGradleDistribution {
+
+    public UnderDevelopmentGradleDistribution() {
+        this(new IntegrationTestBuildContext());
+    }
+
+    private UnderDevelopmentGradleDistribution(IntegrationTestBuildContext buildContext) {
+        super(
+                buildContext.getVersion(),
+                buildContext.getGradleHomeDir(),
+                buildContext.getDistributionsDir().file(String.format("gradle-%s-bin.zip", buildContext.getVersion().getVersion()))
+        );
+    }
+
+}
+
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/UnexpectedBuildFailure.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/UnexpectedBuildFailure.java
new file mode 100644
index 0000000..1f10a86
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/UnexpectedBuildFailure.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.executer;
+
+public class UnexpectedBuildFailure extends RuntimeException {
+    public UnexpectedBuildFailure(String message) {
+        super(message);
+    }
+
+    public UnexpectedBuildFailure(Exception e) {
+        super(e);
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ClasspathVersionJsonSource.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ClasspathVersionJsonSource.java
new file mode 100644
index 0000000..c778ae0
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ClasspathVersionJsonSource.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.versions;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.internal.Factory;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+
+public class ClasspathVersionJsonSource implements Factory<Reader> {
+
+    private final String resourceName;
+    private final ClassLoader classLoader;
+
+    public ClasspathVersionJsonSource() {
+        this("all-released-versions.json", ClasspathVersionJsonSource.class.getClassLoader());
+    }
+
+    ClasspathVersionJsonSource(String resourceName, ClassLoader classLoader) {
+        this.resourceName = resourceName;
+        this.classLoader = classLoader;
+    }
+
+    public Reader create() {
+        URL resource = classLoader.getResource(resourceName);
+        if (resource == null) {
+            throw new RuntimeException(
+                    "Unable to find the released versions information.\n"
+                            + "The resource '" + resourceName + "' was not found.\n"
+                            + "Most likely, you haven't ran the 'prepareVersionsInfo' task.\n"
+                            + "If you have trouble running tests from your IDE, please run gradlew idea|eclipse first."
+            );
+        }
+
+        try {
+            return new InputStreamReader(resource.openStream(), "utf8");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/IsTestableGradleVersionSpec.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/IsTestableGradleVersionSpec.groovy
new file mode 100644
index 0000000..d897f3a
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/IsTestableGradleVersionSpec.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.versions
+
+import org.gradle.api.specs.Spec
+import org.gradle.util.GradleVersion
+
+import static org.gradle.integtests.fixtures.versions.ReleasedGradleVersion.Type.NIGHTLY
+import static org.gradle.integtests.fixtures.versions.ReleasedGradleVersion.Type.RELEASE_CANDIDATE
+
+class IsTestableGradleVersionSpec implements Spec<ReleasedGradleVersion> {
+
+    def lowestInterestingVersion = GradleVersion.version("0.8")
+    def excludedVersions = ["0.9-rc-1", "0.9-rc-2", "1.0-milestone-4"].collect { GradleVersion.version(it) }
+    def currentVersion = GradleVersion.current()
+
+    boolean isSatisfiedBy(ReleasedGradleVersion releasedGradleVersion) {
+        return releasedGradleVersion.version != currentVersion &&
+                !(releasedGradleVersion.version in excludedVersions) &&
+                releasedGradleVersion.version >= lowestInterestingVersion &&
+                releasedGradleVersion.type != NIGHTLY &&
+                (releasedGradleVersion.type != RELEASE_CANDIDATE || releasedGradleVersion.current) // only the current/active RC if there is one
+    }
+
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ReleasedGradleVersion.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ReleasedGradleVersion.java
new file mode 100644
index 0000000..11d8b58
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ReleasedGradleVersion.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.versions;
+
+import org.gradle.util.GradleVersion;
+
+public class ReleasedGradleVersion implements Comparable<ReleasedGradleVersion> {
+
+    private final GradleVersion version;
+    private final Type type;
+    private final boolean current;
+
+    public ReleasedGradleVersion(GradleVersion version, Type type, boolean current) {
+        this.version = version;
+        this.type = type;
+        this.current = current;
+    }
+
+    @Override
+    public String toString() {
+        return "ReleasedGradleVersion{"
+                + "version=" + version
+                + ", type=" + type
+                + ", current=" + current
+                + '}';
+    }
+
+    public GradleVersion getVersion() {
+        return version;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    public boolean isCurrent() {
+        return current;
+    }
+
+    public static enum Type {
+        NIGHTLY,
+        RELEASE_CANDIDATE,
+        FINAL
+    }
+
+    public int compareTo(ReleasedGradleVersion o) {
+        return this.getVersion().compareTo(o.getVersion());
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ReleasedVersionDistributions.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ReleasedVersionDistributions.java
new file mode 100644
index 0000000..a77c617
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/ReleasedVersionDistributions.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.versions;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.specs.Spec;
+import org.gradle.integtests.fixtures.executer.GradleDistribution;
+import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext;
+import org.gradle.internal.Factory;
+import org.gradle.util.CollectionUtils;
+import org.gradle.util.GradleVersion;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.gradle.integtests.fixtures.versions.ReleasedGradleVersion.Type.FINAL;
+import static org.gradle.util.CollectionUtils.*;
+
+/**
+ * Provides access to {@link GradleDistribution}s for versions of Gradle that have been released.
+ *
+ * Only versions that are suitable for testing against are made available.
+ *
+ * @see IsTestableGradleVersionSpec
+ */
+public class ReleasedVersionDistributions {
+
+    private final IntegrationTestBuildContext buildContext = new IntegrationTestBuildContext();
+
+    private final Factory<List<ReleasedGradleVersion>> versionsFactory;
+    private List<ReleasedGradleVersion> versions;
+    private List<GradleDistribution> distributions;
+
+    public ReleasedVersionDistributions() {
+        this(new Factory<List<ReleasedGradleVersion>>() {
+            public List<ReleasedGradleVersion> create() {
+                return sort(
+                        filter(
+                                new VersionWebServiceJsonParser(new ClasspathVersionJsonSource()).create(),
+                                new IsTestableGradleVersionSpec()
+                        ),
+                        Collections.reverseOrder()
+                );
+            }
+        });
+    }
+
+    ReleasedVersionDistributions(Factory<List<ReleasedGradleVersion>> versionsFactory) {
+        this.versionsFactory = versionsFactory;
+    }
+
+    private List<ReleasedGradleVersion> getVersions() {
+        if (versions == null) {
+            versions = versionsFactory.create();
+        }
+
+        return versions;
+    }
+
+    public GradleDistribution getMostRecentFinalRelease() {
+        ReleasedGradleVersion mostRecentFinal = findFirst(getVersions(), new Spec<ReleasedGradleVersion>() {
+            public boolean isSatisfiedBy(ReleasedGradleVersion element) {
+                return element.getType() == FINAL;
+            }
+        });
+
+        if (mostRecentFinal == null) {
+            throw new RuntimeException("Unable to get the last version");
+        }
+
+        return buildContext.distribution(mostRecentFinal.getVersion().getVersion());
+    }
+
+    public List<GradleDistribution> getAll() {
+        if (distributions == null) {
+            distributions = CollectionUtils.collect(getVersions(), new Transformer<GradleDistribution, ReleasedGradleVersion>() {
+                public GradleDistribution transform(ReleasedGradleVersion releasedGradleVersion) {
+                    return buildContext.distribution(releasedGradleVersion.getVersion().getVersion());
+                }
+            });
+        }
+        return distributions;
+    }
+
+    public GradleDistribution getDistribution(final GradleVersion gradleVersion) {
+        return findFirst(getAll(), new Spec<GradleDistribution>() {
+            public boolean isSatisfiedBy(GradleDistribution element) {
+                return element.getVersion().equals(gradleVersion);
+            }
+        });
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/VersionWebServiceJsonParser.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/VersionWebServiceJsonParser.java
new file mode 100644
index 0000000..855f483
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/VersionWebServiceJsonParser.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.versions;
+
+import groovy.json.JsonSlurper;
+import org.gradle.api.Transformer;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.internal.Factory;
+import org.gradle.util.GradleVersion;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.List;
+import java.util.Map;
+
+import static org.gradle.integtests.fixtures.versions.ReleasedGradleVersion.Type.*;
+import static org.gradle.util.CollectionUtils.collect;
+
+// This can't be implemented in Groovy because it can't handle the generic interface implementation
+public class VersionWebServiceJsonParser implements Factory<List<ReleasedGradleVersion>> {
+
+    private final Factory<Reader> jsonSource;
+
+    public VersionWebServiceJsonParser(Factory<Reader> jsonSource) {
+        this.jsonSource = jsonSource;
+    }
+
+    public List<ReleasedGradleVersion> create() {
+        List<Map<String, ?>> list = readJson();
+        return collect(list, new Transformer<ReleasedGradleVersion, Map<String, ?>>() {
+            public ReleasedGradleVersion transform(Map<String, ?> jsonEntry) {
+                ReleasedGradleVersion.Type type;
+                boolean current;
+
+                boolean jsonNightly = (Boolean) jsonEntry.get("nightly");
+                String jsonRcFor = (String) jsonEntry.get("rcFor");
+                boolean jsonActiveRc = (Boolean) jsonEntry.get("activeRc");
+                boolean jsonCurrent = (Boolean) jsonEntry.get("current");
+                String jsonVersion = (String) jsonEntry.get("version");
+
+                if (jsonNightly) {
+                    type = NIGHTLY;
+                    current = true;
+                } else if (jsonRcFor != null && jsonRcFor.length() > 0) {
+                    type = RELEASE_CANDIDATE;
+                    current = jsonActiveRc;
+                } else {
+                    type = FINAL;
+                    current = jsonCurrent;
+                }
+
+                return new ReleasedGradleVersion(GradleVersion.version(jsonVersion), type, current);
+            }
+        });
+    }
+
+    private List<Map<String, ?>> readJson() {
+        Reader reader = jsonSource.create();
+        try {
+            //noinspection unchecked
+            return (List<Map<String, ?>>) new JsonSlurper().parse(reader);
+        } finally {
+            try {
+                reader.close();
+            } catch (IOException e) {
+                //noinspection ThrowFromFinallyBlock
+                throw new UncheckedIOException(e);
+            }
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/VersionsInfo.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/VersionsInfo.groovy
deleted file mode 100644
index 907874c..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/VersionsInfo.groovy
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures.versions
-
-import groovy.json.JsonSlurper
-import org.gradle.util.GradleVersion
-import static org.gradle.util.GradleVersion.version
-
-/**
- * by Szczepan Faber, created at: 3/12/12
- */
-class VersionsInfo {
-    
-    String lowestInterestingVersion = "0.8"
-    List<String> excludedVersions = ["0.9-rc-1", "0.9-rc-2", "1.0-milestone-4", GradleVersion.current().version]
-
-    Closure getVersionsJson = {
-        def fileName = "all-released-versions.json"
-        def resource = getClass().classLoader.getResource(fileName)
-        if (resource == null) {
-            throw new RuntimeException("""Unable to find the released versions information.
-The resource '$fileName' was not found.
-Most likely, you haven't ran the 'prepareVersionsInfo' task.
-If you have trouble running tests from your IDE, please run gradlew idea|eclipse first.
-""")
-        }
-        def jsonText = resource.text
-        return new JsonSlurper().parseText(jsonText)
-    }
-
-    /**
-     * @return the versions we are interested in covering in our cross-compatibility tests.
-     * Always contains the current release. May contain the RC. Never contains nightly.
-     * Excludes 'current' version. Excludes certain less-interesting versions. The list is ordered,
-     * latest version first.
-     */
-    List<String> getVersions() {
-        def json = getVersionsJson()
-
-        def rc = json.find { it.rcFor }
-        def current = json.find { it.current }
-
-        def out = new LinkedHashSet<String>()
-
-        //exclude nightly and old versions
-        def releases = json.findAll { !it.nightly && version(it.version) >= version(lowestInterestingVersion) &&
-                !excludedVersions.contains(it.version) }
-
-        out += releases.collect { it.version }
-
-        if (rc) {
-            if (rc.rcFor == current.version) {
-                //RC released - remove from list:
-                out.remove(rc.version)
-            } else {
-                //if the RC has not been already released it should be higher then the current
-                assert version(rc.version) > version(current.version)
-            }
-        }
-
-        def sorted = out.sort( { a,b -> version(b).compareTo(version(a)) })
-        new LinkedList(sorted)
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptor.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptor.groovy
index 34c9092..1f5e553 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptor.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptor.groovy
@@ -24,11 +24,17 @@ class IvyDescriptor {
     final Map<String, IvyDescriptorDependencyConfiguration> dependencies = [:]
     Map<String, IvyDescriptorArtifact> artifacts = [:]
     Map<String, IvyDescriptorConfiguration> configurations = [:]
-    String rev
+    String organisation
+    String module
+    String revision
+    String description
 
     IvyDescriptor(File ivyFile) {
         def ivy = new XmlParser().parse(ivyFile)
-        rev = ivy. at rev
+        organisation = ivy.info[0]. at organisation
+        module = ivy.info[0]. at module
+        revision = ivy.info[0]. at revision
+        description = ivy.info[0].description[0]?.text()
 
         ivy.configurations[0].conf.each {
             configurations[it. at name] = new IvyDescriptorConfiguration(
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyFileModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyFileModule.groovy
index 91e007f..1a1ed52 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyFileModule.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyFileModule.groovy
@@ -17,7 +17,7 @@ package org.gradle.test.fixtures.ivy
 
 import org.apache.ivy.core.IvyPatternHelper
 import org.apache.ivy.core.module.id.ModuleRevisionId
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.hash.HashUtil
 
 class IvyFileModule extends AbstractIvyModule {
@@ -61,7 +61,7 @@ class IvyFileModule extends AbstractIvyModule {
         return this
     }
 
-    IvyFileModule dependsOn(String ... modules) {
+    IvyFileModule dependsOn(String... modules) {
         modules.each { dependsOn(organisation, it, revision) }
         return this
     }
@@ -96,7 +96,7 @@ class IvyFileModule extends AbstractIvyModule {
     }
 
     TestFile artifactFile(String name) {
-        return file(artifacts.find{ it.name == name })
+        return file(artifacts.find { it.name == name })
     }
 
     /**
@@ -166,7 +166,7 @@ class IvyFileModule extends AbstractIvyModule {
         return this
     }
 
-    private TestFile file(artifact) {
+    TestFile file(artifact) {
         return moduleDir.file("${artifact.name}-${revision}${artifact.classifier ? '-' + artifact.classifier : ''}.${artifact.type}")
     }
 
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyFileRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyFileRepository.groovy
index c017f50..6abdc4d 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyFileRepository.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyFileRepository.groovy
@@ -17,7 +17,7 @@ package org.gradle.test.fixtures.ivy
 
 import org.apache.ivy.core.IvyPatternHelper
 import org.apache.ivy.core.module.id.ModuleRevisionId
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
 
 class IvyFileRepository implements IvyRepository {
     final TestFile rootDir
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyHttpModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyHttpModule.groovy
index e6af28c..68aa989 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyHttpModule.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyHttpModule.groovy
@@ -16,8 +16,8 @@
 
 package org.gradle.test.fixtures.ivy
 
+import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.server.http.HttpServer
-import org.gradle.util.TestFile
 
 class IvyHttpModule extends AbstractIvyModule {
     private final IvyFileModule backingModule
@@ -88,10 +88,18 @@ class IvyHttpModule extends AbstractIvyModule {
         server.expectGetMissing("$prefix/$ivyFile.name")
     }
 
+    void expectIvyGetBroken() {
+        server.expectGetBroken("$prefix/$ivyFile.name")
+    }
+
     void expectIvyHead() {
         server.expectHead("$prefix/$ivyFile.name", ivyFile)
     }
 
+    void expectIvyHeadBroken() {
+        server.expectHeadBroken("$prefix/$ivyFile.name")
+    }
+
     void expectIvySha1Get() {
         server.expectGet("$prefix/${ivyFile.name}.sha1", backingModule.sha1File(ivyFile))
     }
@@ -104,10 +112,22 @@ class IvyHttpModule extends AbstractIvyModule {
         server.expectGet("$prefix/$jarFile.name", jarFile)
     }
 
+    void expectJarGetMissing() {
+        server.expectGetMissing("$prefix/$jarFile.name")
+    }
+
+    void expectJarGetBroken() {
+        server.expectGetBroken("$prefix/$jarFile.name")
+    }
+
     void expectJarHead() {
         server.expectHead("$prefix/$jarFile.name", jarFile)
     }
 
+    void expectJarHeadMissing() {
+        server.expectHeadMissing("$prefix/$jarFile.name")
+    }
+
     void expectJarSha1Get() {
         server.expectGet("$prefix/${jarFile.name}.sha1", backingModule.sha1File(jarFile))
     }
@@ -121,12 +141,29 @@ class IvyHttpModule extends AbstractIvyModule {
         server.expectGet("$prefix/$artifactFile.name", artifactFile)
     }
 
+    void expectArtifactGet(Map options) {
+        def mappedOptions = [name: options.name ?: module, type: options.type ?: 'jar', classifier: options.classifier ?: null]
+        def artifactFile = backingModule.file(mappedOptions)
+        server.expectGet("$prefix/$artifactFile.name", artifactFile)
+    }
+
     void expectPut(String username, String password, File dir, String... artifactNames) {
         artifactNames.each {
             server.expectPut("$prefix/$it", username, password, new File(dir, it))
         }
     }
 
+    void expectArtifactHead(Map options) {
+        def mappedOptions = [name: options.name ?: module, type: options.type ?: 'jar', classifier: options.classifier ?: null]
+        def artifactFile = backingModule.file(mappedOptions)
+        server.expectHead("$prefix/$artifactFile.name", artifactFile)
+    }
+
+    void expectArtifactSha1Get(Map options) {
+        def mappedOptions = [name: options.name ?: module, type: options.type ?: 'jar', classifier: options.classifier ?: null]
+        def artifactFile = backingModule.file(mappedOptions)
+        server.expectGet("$prefix/${artifactFile.name}.sha1", backingModule.sha1File(artifactFile))
+    }
 }
 
 
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyHttpRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyHttpRepository.groovy
index 6c76e71..ed777db 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyHttpRepository.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyHttpRepository.groovy
@@ -48,6 +48,14 @@ class IvyHttpRepository implements IvyRepository {
         server.expectGetDirectoryListing("$contextPath/$organisation/$module/", backingRepository.module(organisation, module, "1.0").moduleDir.parentFile)
     }
 
+    void expectDirectoryListGetMissing(String organisation, String module) {
+        server.expectGetMissing("$contextPath/$organisation/$module/")
+    }
+
+    void expectDirectoryListGetBroken(String organisation, String module) {
+        server.expectGetBroken("$contextPath/$organisation/$module/")
+    }
+
     IvyHttpModule module(String organisation, String module, Object revision = "1.0") {
         return new IvyHttpModule(server, "$contextPath/$organisation/$module/$revision", backingRepository.module(organisation, module, revision))
     }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyModule.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyModule.java
index 324f858..daa8b45 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyModule.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyModule.java
@@ -16,7 +16,7 @@
 
 package org.gradle.test.fixtures.ivy;
 
-import org.gradle.util.TestFile;
+import org.gradle.test.fixtures.file.TestFile;
 
 import java.util.Map;
 
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/BasicHttpResource.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/BasicHttpResource.groovy
new file mode 100644
index 0000000..80fba5b
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/BasicHttpResource.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.maven
+
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.server.http.HttpServer
+
+class BasicHttpResource extends HttpResource{
+    private final String path
+    private final File file
+
+    BasicHttpResource(HttpServer httpServer, File file, String path) {
+        super(httpServer)
+        this.file = file
+        this.path = path
+    }
+
+    @Override
+    void expectGetMissing() {
+        server.expectGetMissing(path)
+    }
+
+    @Override
+    TestFile getFile() {
+        return file
+    }
+
+    @Override
+    protected String getPath() {
+        return path
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/DefaultMavenMetaData.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/DefaultMavenMetaData.groovy
new file mode 100644
index 0000000..7976997
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/DefaultMavenMetaData.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.maven
+
+/**
+ * http://maven.apache.org/ref/3.0.1/maven-repository-metadata/repository-metadata.html
+ */
+class DefaultMavenMetaData implements MavenMetaData{
+
+    String text
+
+    String groupId
+    String artifactId
+    String version
+
+    List<String> versions = []
+    String lastUpdated
+
+    DefaultMavenMetaData(File file) {
+        text = file.text
+        def xml = new XmlParser().parseText(text)
+
+        groupId = xml.groupId[0]?.text()
+        artifactId = xml.artifactId[0]?.text()
+        version = xml.version[0]?.text()
+
+        def versioning = xml.versioning[0]
+
+        lastUpdated = versioning.lastUpdated[0]?.text()
+
+        versioning.versions[0].version.each {
+            versions << it.text()
+        }
+    }
+
+
+
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/HttpArtifact.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/HttpArtifact.groovy
new file mode 100644
index 0000000..8ee2afe
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/HttpArtifact.groovy
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.maven
+
+import org.gradle.api.artifacts.repositories.PasswordCredentials
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.gradle.util.hash.HashUtil
+
+abstract class HttpArtifact extends HttpResource {
+
+    String modulePath
+
+    public HttpArtifact(HttpServer server, String modulePath) {
+        super(server)
+        this.modulePath = modulePath
+    }
+
+    void expectHeadMissing() {
+        server.expectHeadMissing(path)
+    }
+
+    void expectGetMissing(PasswordCredentials passwordCredentials = null) {
+        server.expectGetMissing(path, passwordCredentials)
+    }
+
+    HttpResource getMd5() {
+        return new BasicHttpResource(server, getMd5File(), "${path}.md5")
+    }
+
+    HttpResource getSha1() {
+        return new BasicHttpResource(server, getSha1File(), "${path}.sha1")
+    }
+
+    protected String getPath() {
+        "${modulePath}/${file.name}"
+    }
+
+    protected abstract TestFile getSha1File();
+
+    protected abstract TestFile getMd5File();
+
+    abstract TestFile getFile();
+
+    void verifyChecksums() {
+        def sha1File = getSha1File()
+        sha1File.assertIsFile()
+        assert new BigInteger(sha1File.text, 16) == new BigInteger(getHash(getFile(), "sha1"), 16)
+        def md5File = getMd5File()
+        md5File.assertIsFile()
+        assert new BigInteger(md5File.text, 16) == new BigInteger(getHash(getFile(), "md5"), 16)
+    }
+
+    protected String getHash(TestFile file, String algorithm) {
+        HashUtil.createHash(file, algorithm.toUpperCase()).asHexString()
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/HttpResource.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/HttpResource.groovy
new file mode 100644
index 0000000..be5b18d
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/HttpResource.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.maven
+
+import org.gradle.api.artifacts.repositories.PasswordCredentials
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.server.http.HttpServer
+
+abstract class HttpResource {
+
+    protected HttpServer server
+
+    public HttpResource(HttpServer server) {
+        this.server = server
+    }
+
+    void expectGet() {
+        server.expectGet(getPath(), file)
+    }
+
+    void expectGetBroken() {
+        server.expectGetBroken(getPath())
+    }
+
+    void expectHead() {
+        server.expectHead(getPath(), file)
+    }
+
+    void expectPut(PasswordCredentials credentials) {
+        expectPut(200, credentials)
+    }
+
+    void expectPut(Integer statusCode = 200, PasswordCredentials credentials = null) {
+        server.expectPut(getPath(), getFile(), statusCode, credentials)
+    }
+
+    abstract void expectGetMissing();
+
+    abstract TestFile getFile();
+
+    abstract protected String getPath();
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/M2Installation.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/M2Installation.groovy
new file mode 100644
index 0000000..d7c744d
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/M2Installation.groovy
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.test.fixtures.maven
+
+import org.gradle.api.Action
+import org.gradle.integtests.fixtures.executer.GradleExecuter
+import org.gradle.test.fixtures.file.TestFile
+
+class M2Installation implements Action<GradleExecuter> {
+    final TestFile userHomeDir
+    final TestFile userM2Directory
+    final TestFile userSettingsFile
+    final TestFile globalMavenDirectory
+
+    public M2Installation(TestFile m2Directory) {
+        userHomeDir = m2Directory.createDir("maven_home")
+        userM2Directory = userHomeDir.createDir(".m2")
+        userSettingsFile = userM2Directory.file("settings.xml")
+        globalMavenDirectory = userHomeDir.createDir("m2_home")
+    }
+
+    MavenFileRepository mavenRepo() {
+        new MavenFileRepository(userM2Directory.file("repository"))
+    }
+
+    M2Installation generateUserSettingsFile(MavenFileRepository userRepository) {
+        userSettingsFile.text = """
+<settings>
+    <localRepository>${userRepository.rootDir.absolutePath}</localRepository>
+</settings>"""
+        return this
+    }
+
+    M2Installation generateGlobalSettingsFile(MavenFileRepository globalRepository = mavenRepo()) {
+        def settings = globalMavenDirectory.file("conf/settings.xml").createFile()
+        settings.text = """
+<settings>
+    <localRepository>${globalRepository.rootDir.absolutePath}</localRepository>
+</settings>"""
+        return this
+    }
+
+    void execute(GradleExecuter executer) {
+        executer.withUserHomeDir(userHomeDir)
+        if (globalMavenDirectory?.exists()) {
+            executer.withEnvironmentVars(M2_HOME:globalMavenDirectory.absolutePath)
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenFileModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenFileModule.groovy
new file mode 100644
index 0000000..7ee204a
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenFileModule.groovy
@@ -0,0 +1,354 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.test.fixtures.maven
+
+import groovy.xml.MarkupBuilder
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.hash.HashUtil
+
+import java.text.SimpleDateFormat
+
+class MavenFileModule implements MavenModule {
+    final TestFile moduleDir
+    final String groupId
+    final String artifactId
+    final String version
+    private String parentPomSection
+    String type = 'jar'
+    String packaging
+    private final List dependencies = []
+    int publishCount = 1
+    final updateFormat = new SimpleDateFormat("yyyyMMddHHmmss")
+    final timestampFormat = new SimpleDateFormat("yyyyMMdd.HHmmss")
+    private final List artifacts = []
+    private boolean uniqueSnapshots = true;
+
+    MavenFileModule(TestFile moduleDir, String groupId, String artifactId, String version) {
+        this.moduleDir = moduleDir
+        this.groupId = groupId
+        this.artifactId = artifactId
+        this.version = version
+    }
+
+    MavenFileModule parent(String group, String artifactId, String version) {
+        parentPomSection = """
+<parent>
+  <groupId>${group}</groupId>
+  <artifactId>${artifactId}</artifactId>
+  <version>${version}</version>
+</parent>
+"""
+        return this
+    }
+
+    MavenFileModule dependsOn(String... dependencyArtifactIds) {
+        for (String id : dependencyArtifactIds) {
+            dependsOn(groupId, id, '1.0')
+        }
+        return this
+    }
+
+    MavenFileModule dependsOn(String group, String artifactId, String version, String type = null) {
+        this.dependencies << [groupId: group, artifactId: artifactId, version: version, type: type]
+        return this
+    }
+
+    MavenFileModule hasPackaging(String packaging) {
+        this.packaging = packaging
+        return this
+    }
+
+    /**
+     * Specifies the type of the main artifact.
+     */
+    MavenFileModule hasType(String type) {
+        this.type = type
+        return this
+    }
+
+    /**
+     * Adds an additional artifact to this module.
+     * @param options Can specify any of: type or classifier
+     */
+    MavenFileModule artifact(Map<String, ?> options) {
+        artifacts << options
+        return this
+    }
+
+    MavenFileModule withNonUniqueSnapshots() {
+        uniqueSnapshots = false;
+        return this;
+    }
+
+    void assertNotPublished() {
+        pomFile.assertDoesNotExist()
+    }
+
+    void assertPublishedAsJavaModule() {
+        assert pomFile.assertExists()
+        assert parsedPom.groupId == groupId
+        assert parsedPom.artifactId == artifactId
+        assert parsedPom.version == version
+        assertArtifactsPublished("${artifactId}-${version}.jar", "${artifactId}-${version}.pom")
+    }
+
+    /**
+     * Asserts that exactly the given artifacts have been deployed, along with their checksum files
+     */
+    void assertArtifactsPublished(String... names) {
+        def artifactNames = names
+        if (uniqueSnapshots && version.endsWith('-SNAPSHOT')) {
+            def metaData = new XmlParser().parse(moduleDir.file('maven-metadata.xml'))
+            def timestamp = metaData.versioning.snapshot.timestamp[0].text().trim()
+            def build = metaData.versioning.snapshot.buildNumber[0].text().trim()
+            artifactNames = names.collect { it.replace('-SNAPSHOT', "-${timestamp}-${build}")}
+            artifactNames.add("maven-metadata.xml")
+        }
+        assert moduleDir.isDirectory()
+        Set actual = moduleDir.list() as Set
+        for (name in artifactNames) {
+            assert actual.remove(name)
+            assert actual.remove("${name}.md5" as String)
+            assert actual.remove("${name}.sha1" as String)
+        }
+        assert actual.isEmpty()
+    }
+
+    MavenPom getParsedPom() {
+        return new MavenPom(pomFile)
+    }
+
+    DefaultMavenMetaData getRootMetaData() {
+        new DefaultMavenMetaData(rootMetaDataFile)
+    }
+
+    TestFile getPomFile() {
+        return moduleDir.file("$artifactId-${publishArtifactVersion}.pom")
+    }
+
+    TestFile getMetaDataFile() {
+        moduleDir.file("maven-metadata.xml")
+    }
+
+    TestFile getRootMetaDataFile() {
+        moduleDir.parentFile.file("maven-metadata.xml")
+    }
+
+    TestFile getArtifactFile(Map options = [:]) {
+        if (version.endsWith("-SNAPSHOT") && !metaDataFile.exists() && uniqueSnapshots) {
+            def artifact = toArtifact(options)
+            return moduleDir.file("${artifactId}-${version}${artifact.classifier ? "-${artifact.classifier}" : ""}.${artifact.type}")
+        }
+        return artifactFile(options)
+    }
+
+    TestFile getArtifactSha1File() {
+        return getSha1File(artifactFile)
+    }
+
+    TestFile getArtifactMd5File() {
+        return getMd5File(artifactFile)
+    }
+
+    TestFile artifactFile(Map<String, ?> options) {
+        def artifact = toArtifact(options)
+        def fileName = "$artifactId-${publishArtifactVersion}.${artifact.type}"
+        if (artifact.classifier) {
+            fileName = "$artifactId-$publishArtifactVersion-${artifact.classifier}.${artifact.type}"
+        }
+        return moduleDir.file(fileName)
+    }
+
+    MavenFileModule publishWithChangedContent() {
+        publishCount++
+        return publish()
+    }
+
+    String getPublishArtifactVersion() {
+        if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
+            return "${version.replaceFirst('-SNAPSHOT$', '')}-${timestampFormat.format(publishTimestamp)}-${publishCount}"
+        }
+        return version
+    }
+
+    Date getPublishTimestamp() {
+        return new Date(updateFormat.parse("20100101120000").time + publishCount * 1000)
+    }
+
+    MavenModule publish() {
+        moduleDir.createDir()
+        def rootMavenMetaData = getRootMetaDataFile()
+
+        updateRootMavenMetaData(rootMavenMetaData)
+        if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
+            def metaDataFile = moduleDir.file('maven-metadata.xml')
+            publish(metaDataFile) {
+                metaDataFile.text = """
+<metadata>
+  <!-- $publishCount -->
+  <groupId>$groupId</groupId>
+  <artifactId>$artifactId</artifactId>
+  <version>$version</version>
+  <versioning>
+    <snapshot>
+      <timestamp>${timestampFormat.format(publishTimestamp)}</timestamp>
+      <buildNumber>$publishCount</buildNumber>
+    </snapshot>
+    <lastUpdated>${updateFormat.format(publishTimestamp)}</lastUpdated>
+  </versioning>
+</metadata>
+"""
+            }
+        }
+
+        publish(pomFile) {
+            def pomPackaging = packaging ?: type;
+            pomFile.text = ""
+            pomFile << """
+<project xmlns="http://maven.apache.org/POM/4.0.0">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>$groupId</groupId>
+  <artifactId>$artifactId</artifactId>
+  <packaging>$pomPackaging</packaging>
+  <version>$version</version>
+  <description>Published on $publishTimestamp</description>"""
+
+            if (parentPomSection) {
+                pomFile << "\n$parentPomSection\n"
+            }
+
+            if (!dependencies.empty) {
+                pomFile << """
+  <dependencies>"""
+            }
+
+            dependencies.each { dependency ->
+                def typeAttribute = dependency['type'] == null ? "" : "<type>$dependency.type</type>"
+                pomFile << """
+    <dependency>
+      <groupId>$dependency.groupId</groupId>
+      <artifactId>$dependency.artifactId</artifactId>
+      <version>$dependency.version</version>
+      $typeAttribute
+    </dependency>"""
+            }
+
+            if (!dependencies.empty) {
+                pomFile << """
+  </dependencies>"""
+            }
+
+            pomFile << "\n</project>"
+        }
+
+        artifacts.each { artifact ->
+            publishArtifact(artifact)
+        }
+        publishArtifact([:])
+        return this
+    }
+
+    private void updateRootMavenMetaData(TestFile rootMavenMetaData) {
+        def allVersions = rootMavenMetaData.exists() ? new XmlParser().parseText(rootMavenMetaData.text).versioning.versions.version*.value().flatten() : []
+        allVersions << version;
+        publish(rootMavenMetaData) {
+            rootMavenMetaData.withWriter {writer ->
+                def builder = new MarkupBuilder(writer)
+                builder.metadata {
+                    groupId(groupId)
+                    artifactId(artifactId)
+                    version(allVersions.max())
+                    versioning {
+                        if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
+                            snapshot {
+                                timestamp(timestampFormat.format(publishTimestamp))
+                                buildNumber(publishCount)
+                                lastUpdated(updateFormat.format(publishTimestamp))
+                            }
+                        } else {
+                            versions {
+                                allVersions.each {currVersion ->
+                                    version(currVersion)
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private File publishArtifact(Map<String, ?> artifact) {
+        def artifactFile = artifactFile(artifact)
+        publish(artifactFile) {
+            if (type != 'pom') {
+                artifactFile.text = "${artifactFile.name} : $publishCount"
+            }
+        }
+        return artifactFile
+    }
+
+    private publish(File file, Closure cl) {
+        def lastModifiedTime = file.exists() ? file.lastModified() : null
+        cl.call(file)
+        if (lastModifiedTime != null) {
+            file.setLastModified(lastModifiedTime + 2000)
+        }
+        createHashFiles(file)
+    }
+
+    private Map<String, Object> toArtifact(Map<String, ?> options) {
+        options = new HashMap<String, Object>(options)
+        def artifact = [type: options.remove('type') ?: type, classifier: options.remove('classifier') ?: null]
+        assert options.isEmpty(): "Unknown options : ${options.keySet()}"
+        return artifact
+    }
+
+    private void createHashFiles(File file) {
+        sha1File(file)
+        md5File(file)
+    }
+
+    TestFile getSha1File(File file) {
+        getHashFile(file, "sha1")
+    }
+
+    TestFile sha1File(File file) {
+        hashFile(file, "sha1");
+    }
+
+    TestFile getMd5File(File file) {
+        getHashFile(file, "md5")
+    }
+
+    TestFile md5File(File file) {
+        hashFile(file, "md5")
+    }
+
+    private TestFile hashFile(TestFile file, String algorithm) {
+        def hashFile = getHashFile(file, algorithm)
+        hashFile.text = getHash(file, algorithm)
+        return hashFile
+    }
+
+    protected TestFile getHashFile(TestFile file, String algorithm) {
+        file.parentFile.file("${file.name}.${algorithm}")
+    }
+
+    protected String getHash(TestFile file, String algorithm) {
+        HashUtil.createHash(file, algorithm.toUpperCase()).asHexString()
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenFileRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenFileRepository.groovy
new file mode 100644
index 0000000..eef54df
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenFileRepository.groovy
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.test.fixtures.maven
+
+import org.gradle.test.fixtures.file.TestFile
+
+/**
+ * A fixture for dealing with local Maven repositories.
+ */
+class MavenFileRepository implements MavenRepository {
+    final TestFile rootDir
+
+    MavenFileRepository(TestFile rootDir) {
+        this.rootDir = rootDir
+    }
+
+    URI getUri() {
+        return rootDir.toURI()
+    }
+
+    MavenFileModule module(String groupId, String artifactId, Object version = '1.0') {
+        def artifactDir = rootDir.file("${groupId.replace('.', '/')}/$artifactId/$version")
+        return new MavenFileModule(artifactDir, groupId, artifactId, version as String)
+    }
+}
+
+
+
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenHttpArtifact.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenHttpArtifact.groovy
new file mode 100644
index 0000000..ca35fbf
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenHttpArtifact.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.maven
+
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.server.http.HttpServer
+
+class MavenHttpArtifact extends HttpArtifact {
+    private final MavenFileModule backingModule;
+    private final Map options
+
+    public MavenHttpArtifact(HttpServer server, String modulePath, MavenFileModule backingModule, Map<String, ?> options = [:]) {
+        super(server, modulePath)
+        this.options = options
+        this.backingModule = backingModule;
+    }
+
+    @Override
+    TestFile getSha1File() {
+        backingModule.getSha1File(file)
+    }
+
+    @Override
+    TestFile getMd5File() {
+        backingModule.getMd5File(file)
+    }
+
+    TestFile getFile() {
+        return backingModule.getArtifactFile(options)
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenHttpModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenHttpModule.groovy
new file mode 100644
index 0000000..7e747fe
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenHttpModule.groovy
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.test.fixtures.maven
+
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.server.http.HttpServer
+
+class MavenHttpModule implements MavenModule {
+    private final HttpServer server
+    private final String moduleRootPath
+    private final MavenFileModule backingModule
+
+    MavenHttpModule(HttpServer server, String repoRoot, MavenFileModule backingModule) {
+        this.backingModule = backingModule
+        this.server = server
+        this.moduleRootPath = "${repoRoot}/${backingModule.groupId.replace('.', '/')}/${backingModule.artifactId}"
+    }
+
+    protected String getModuleVersionPath() {
+        "${moduleRootPath}/${backingModule.version}"
+    }
+
+    HttpArtifact getArtifact(Map options = [:]) {
+        return new MavenHttpArtifact(server, "${moduleRootPath}/${backingModule.version}", backingModule, options)
+    }
+
+    /**
+     * Adds an additional artifact to this module.
+     * @param options Can specify any of: type or classifier
+     */
+    HttpArtifact artifact(Map<String, ?> options) {
+        backingModule.artifact(options)
+        return new MavenHttpArtifact(server, "${moduleRootPath}/${backingModule.version}", backingModule, options)
+    }
+
+    MavenHttpModule publish() {
+        backingModule.publish()
+        return this
+    }
+
+    MavenHttpModule publishWithChangedContent() {
+        backingModule.publishWithChangedContent()
+        return this
+    }
+
+    MavenHttpModule withNonUniqueSnapshots() {
+        backingModule.withNonUniqueSnapshots()
+        return this
+    }
+
+    MavenModule parent(String group, String artifactId, String version) {
+        backingModule.parent(group, artifactId, version)
+        return this
+    }
+
+    MavenHttpModule dependsOn(String group, String artifactId, String version) {
+        backingModule.dependsOn(group, artifactId, version)
+        return this
+    }
+
+    MavenModule hasPackaging(String packaging) {
+        backingModule.hasPackaging(packaging)
+        return this
+    }
+
+    TestFile getPomFile() {
+        return backingModule.pomFile
+    }
+
+    TestFile getArtifactFile(Map options = [:]) {
+        return backingModule.getArtifactFile(options)
+    }
+
+    TestFile getMetaDataFile() {
+        return backingModule.metaDataFile
+    }
+
+    MavenPom getParsedPom() {
+        return backingModule.parsedPom;
+    }
+
+    PomHttpArtifact getPom() {
+        return new PomHttpArtifact(server, getModuleVersionPath(), backingModule)
+    }
+
+    MetaDataArtifact getRootMetaData() {
+        return new MetaDataArtifact(server, "$moduleRootPath", backingModule)
+    }
+
+    TestFile getRootMetaDataFile() {
+        return backingModule.rootMetaDataFile
+    }
+
+    void allowAll() {
+        server.allowGetOrHead(moduleVersionPath, backingModule.moduleDir)
+    }
+
+    HttpResource getMetaData() {
+        return new BasicHttpResource(server, metaDataFile, getMetaDataPath())
+    }
+
+    String getMetaDataPath() {
+        "$moduleVersionPath/$metaDataFile.name"
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenHttpRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenHttpRepository.groovy
new file mode 100644
index 0000000..ad63afa
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenHttpRepository.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.test.fixtures.maven
+
+import org.gradle.test.fixtures.server.http.HttpServer
+
+/**
+ * A fixture for dealing with remote HTTP Maven repositories.
+ */
+class MavenHttpRepository {
+    private final HttpServer server
+    private final MavenFileRepository backingRepository
+    private final String contextPath
+
+    MavenHttpRepository(HttpServer server, String contextPath = "/repo", MavenFileRepository backingRepository) {
+        if (!contextPath.startsWith("/")) {
+            throw new IllegalArgumentException("Context path must start with '/'")
+        }
+        this.contextPath = contextPath
+        this.server = server
+        this.backingRepository = backingRepository
+    }
+
+    URI getUri() {
+        return new URI("http://localhost:${server.port}${contextPath}")
+    }
+
+    void expectMetaDataGet(String groupId, String artifactId) {
+        def path = "${groupId.replace('.', '/')}/$artifactId/maven-metadata.xml"
+        server.expectGet("$contextPath/$path", backingRepository.getRootDir().file(path))
+    }
+
+    void expectMetaDataGetMissing(String groupId, String artifactId) {
+        def path = "${groupId.replace('.', '/')}/$artifactId/maven-metadata.xml"
+        server.expectGetMissing("$contextPath/$path")
+    }
+
+    void expectDirectoryListGet(String groupId, String artifactId) {
+        def path = "${groupId.replace('.', '/')}/$artifactId/"
+        server.expectGetDirectoryListing("$contextPath/$path", backingRepository.getRootDir().file(path))
+    }
+
+    MavenHttpModule module(String groupId, String artifactId) {
+        return module(groupId, artifactId, "1.0")
+    }
+
+    MavenHttpModule module(String groupId, String artifactId, Object version) {
+        def backingModule = backingRepository.module(groupId, artifactId, version)
+        return new MavenHttpModule(server, contextPath, backingModule)
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenMetaData.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenMetaData.groovy
new file mode 100644
index 0000000..c046f09
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenMetaData.groovy
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.maven
+
+public interface MavenMetaData {
+    List<String> getVersions();
+
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenModule.groovy
new file mode 100644
index 0000000..5a4dace
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenModule.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.test.fixtures.maven
+
+import org.gradle.test.fixtures.file.TestFile
+
+interface MavenModule {
+    /**
+     * Publishes the pom.xml plus main artifact, plus any additional artifacts for this module.
+     */
+    MavenModule publish()
+
+    /**
+     * Publishes the pom.xml plus main artifact, plus any additional artifacts for this module, with changed content to any
+     * previous publication.
+     */
+    MavenModule publishWithChangedContent()
+
+    MavenModule withNonUniqueSnapshots()
+
+    MavenModule parent(String group, String artifactId, String version)
+
+    MavenModule dependsOn(String group, String artifactId, String version)
+
+    MavenModule hasPackaging(String packaging)
+
+    TestFile getPomFile()
+
+    TestFile getArtifactFile()
+
+    TestFile getMetaDataFile()
+
+    MavenPom getParsedPom()
+
+    MavenMetaData getRootMetaData()
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenPom.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenPom.groovy
new file mode 100644
index 0000000..4c78de4
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenPom.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.maven
+
+class MavenPom {
+    String groupId
+    String artifactId
+    String version
+    String packaging
+    String description
+    final Map<String, MavenScope> scopes = [:]
+
+    MavenPom(File pomFile) {
+        if (pomFile.exists()){
+            def pom = new XmlParser().parse(pomFile)
+
+            groupId = pom.groupId[0]?.text()
+            artifactId = pom.artifactId[0]?.text()
+            version = pom.version[0]?.text()
+            packaging = pom.packaging[0]?.text()
+            description = pom.description[0]?.text()
+
+            pom.dependencies.dependency.each { dep ->
+                def scopeElement = dep.scope
+                def scopeName = scopeElement ? scopeElement.text() : "runtime"
+                def scope = scopes[scopeName]
+                if (!scope) {
+                    scope = new MavenScope()
+                    scopes[scopeName] = scope
+                }
+                scope.addDependency(dep.groupId.text(), dep.artifactId.text(), dep.version.text())
+            }
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenRepository.groovy
new file mode 100644
index 0000000..0a51d3b
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenRepository.groovy
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.test.fixtures.maven
+
+/**
+ * A fixture for dealing with Maven repositories.
+ */
+interface MavenRepository {
+    URI getUri()
+
+    MavenModule module(String groupId, String artifactId)
+
+    MavenModule module(String groupId, String artifactId, Object version)
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenScope.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenScope.groovy
new file mode 100644
index 0000000..10d1b66
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenScope.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.test.fixtures.maven
+
+class MavenScope {
+    final dependencies = []
+
+    void addDependency(String groupId, String artifactId, String version) {
+        dependencies << [groupId: groupId, artifactId: artifactId, version: version]
+    }
+
+    void assertDependsOnArtifacts(String... artifactIds) {
+        assert dependencies.collect { it.artifactId} as Set == artifactIds as Set
+    }
+
+    void assertDependsOn(String groupId, String artifactId, String version) {
+        def dep = [groupId: groupId, artifactId: artifactId, version: version]
+        if (!dependencies.find { it == dep }) {
+            throw new AssertionError("Could not find expected dependency $dep. Actual: $dependencies")
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MetaDataArtifact.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MetaDataArtifact.groovy
new file mode 100644
index 0000000..47fdf61
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MetaDataArtifact.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.maven
+
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.server.http.HttpServer
+
+class MetaDataArtifact extends HttpArtifact implements MavenMetaData {
+    MavenFileModule backingModule
+
+    MetaDataArtifact(HttpServer httpServer, String path, MavenFileModule backingModule) {
+        super(httpServer, path)
+        this.backingModule = backingModule
+    }
+
+    @Override
+    TestFile getSha1File() {
+        backingModule.getSha1File(file)
+    }
+
+    @Override
+    TestFile getMd5File() {
+        backingModule.getMd5File(file)
+    }
+
+    @Override
+    TestFile getFile() {
+        return backingModule.rootMetaDataFile
+    }
+
+    List<String> getVersions() {
+        backingModule.rootMetaData.versions
+    }
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/PomHttpArtifact.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/PomHttpArtifact.groovy
new file mode 100644
index 0000000..41ee404
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/PomHttpArtifact.groovy
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.maven
+
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.server.http.HttpServer
+
+class PomHttpArtifact extends HttpArtifact {
+    MavenFileModule backingModule
+
+    PomHttpArtifact(HttpServer httpServer, String path, MavenFileModule backingModule) {
+        super(httpServer, path)
+        this.backingModule = backingModule
+    }
+
+    @Override
+    void expectGetMissing() {
+        server.expectGetMissing(getPath() - getFile().name + getMissingPomName());
+    }
+
+    @Override
+    TestFile getSha1File() {
+        backingModule.getSha1File(file)
+    }
+
+    @Override
+    TestFile getMd5File() {
+        backingModule.getMd5File(file)
+    }
+
+    @Override
+    TestFile getFile() {
+        return backingModule.pomFile
+    }
+
+    private String getMissingPomName() {
+        if (backingModule.version.endsWith("-SNAPSHOT")) {
+            return "${backingModule.artifactId}-${backingModule.version}.pom"
+        } else {
+            return getFile().name
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/HttpServer.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/HttpServer.groovy
index 4457566..289c797 100755
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/HttpServer.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/HttpServer.groovy
@@ -15,7 +15,9 @@
  */
 package org.gradle.test.fixtures.server.http
 
+import org.gradle.api.artifacts.repositories.PasswordCredentials
 import org.gradle.test.matchers.UserAgentMatcher
+import org.gradle.util.GFileUtils
 import org.gradle.util.hash.HashUtil
 import org.hamcrest.Matcher
 import org.junit.rules.ExternalResource
@@ -118,6 +120,10 @@ class HttpServer extends ExternalResource {
             server.removeConnector(connector)
             throw e
         }
+        def localPort = connector.localPort
+        if (localPort <= 0) {
+            throw new AssertionError("SocketConnector.localPort returned $localPort after starting server");
+        }
     }
 
     void stop() {
@@ -164,6 +170,7 @@ class HttpServer extends ExternalResource {
                 e.assertMet()
             }
         } finally {
+            realm = null
             failure = null
             expectedUserAgent = null
             expections.clear()
@@ -233,22 +240,21 @@ class HttpServer extends ExternalResource {
      * Adds a broken resource at the given URL.
      */
     void addBroken(String path) {
-        allow(path, true, null, new Action() {
-            String getDisplayName() {
-                return "return 500 broken"
-            }
+        allow(path, true, null, broken())
+    }
 
-            void handle(HttpServletRequest request, HttpServletResponse response) {
-                response.sendError(500, "broken")
-            }
-        })
+    /**
+     * Allows one GET request, which fails with a 500 status code
+     */
+    void expectGetBroken(String path) {
+        expect(path, false, ['GET'], broken())
     }
 
     /**
      * Allows one GET request for the given URL, which return 404 status code
      */
-    void expectGetMissing(String path) {
-        expect(path, false, ['GET'], notFound())
+    void expectGetMissing(String path, PasswordCredentials passwordCredentials = null) {
+        expect(path, false, ['GET'], notFound(), passwordCredentials)
     }
 
     /**
@@ -258,6 +264,13 @@ class HttpServer extends ExternalResource {
         expect(path, false, ['HEAD'], notFound())
     }
 
+    /**
+     * Allows one HEAD request for the given URL, which returns a 500 status code
+     */
+    void expectHeadBroken(String path) {
+        expect(path, false, ['HEAD'], broken())
+    }
+
     private Action notFound() {
         new Action() {
             String getDisplayName() {
@@ -270,6 +283,18 @@ class HttpServer extends ExternalResource {
         }
     }
 
+    private Action broken() {
+        new Action() {
+            String getDisplayName() {
+                return "return 500 broken"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
+                response.sendError(500, "broken")
+            }
+        }
+    }
+
     /**
      * Allows one HEAD request for the given URL.
      */
@@ -423,8 +448,8 @@ class HttpServer extends ExternalResource {
     /**
      * Allows one PUT request for the given URL. Writes the request content to the given file.
      */
-    void expectPut(String path, File destFile, int statusCode = HttpStatus.ORDINAL_200_OK) {
-        expect(path, false, ['PUT'], new Action() {
+    void expectPut(String path, File destFile, int statusCode = HttpStatus.ORDINAL_200_OK, PasswordCredentials credentials = null) {
+        def action = new Action() {
             String getDisplayName() {
                 return "write request to $destFile.name and return status $statusCode"
             }
@@ -437,10 +462,13 @@ class HttpServer extends ExternalResource {
                         return;
                     }
                 }
+                GFileUtils.mkdirs(destFile.parentFile)
                 destFile.bytes = request.inputStream.bytes
                 response.setStatus(statusCode)
             }
-        })
+        }
+
+        expect(path, false, ['PUT'], action, credentials)
     }
 
     /**
@@ -506,7 +534,11 @@ class HttpServer extends ExternalResource {
         }
     }
 
-    private void expect(String path, boolean recursive, Collection<String> methods, Action action) {
+    private void expect(String path, boolean recursive, Collection<String> methods, Action action, PasswordCredentials credentials = null) {
+        if (credentials != null) {
+            action = withAuthentication(path, credentials.username, credentials.password, action)
+        }
+
         ExpectOne expectation = new ExpectOne(action, methods, path)
         expections << expectation
         add(path, recursive, methods, new AbstractHandler() {
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/sftp/SFTPServer.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/sftp/SFTPServer.groovy
index fe95bcc..aa909ad 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/sftp/SFTPServer.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/sftp/SFTPServer.groovy
@@ -22,25 +22,24 @@ import org.apache.commons.io.FileUtils
 import org.apache.sshd.SshServer
 import org.apache.sshd.common.NamedFactory
 import org.apache.sshd.common.Session
+import org.apache.sshd.server.*
 import org.apache.sshd.server.command.ScpCommandFactory
 import org.apache.sshd.server.filesystem.NativeFileSystemFactory
 import org.apache.sshd.server.filesystem.NativeSshFile
 import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider
 import org.apache.sshd.server.session.ServerSession
 import org.apache.sshd.server.sftp.SftpSubsystem
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestDirectoryProvider
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.rules.ExternalResource
 
 import java.security.PublicKey
 
-import org.apache.sshd.server.*
-
 class SFTPServer extends ExternalResource {
     final String hostAddress;
     int port
 
-    private final TemporaryFolder tmpDir
+    private final TestDirectoryProvider testDirectoryProvider
     private TestFile baseDir
     private TestFile configDir
 
@@ -49,16 +48,16 @@ class SFTPServer extends ExternalResource {
 
     def fileRequests = [] as Set
 
-    public SFTPServer(TemporaryFolder tmpDir) {
-        this.tmpDir = tmpDir;
+    public SFTPServer(TestDirectoryProvider testDirectoryProvider) {
+        this.testDirectoryProvider = testDirectoryProvider;
         def portFinder = org.gradle.util.AvailablePortFinder.createPrivate()
         port = portFinder.nextAvailable
         this.hostAddress = "127.0.0.1"
     }
 
     protected void before() throws Throwable {
-        baseDir = tmpDir.createDir("sshd/files")
-        configDir = tmpDir.createDir("sshd/config")
+        baseDir = testDirectoryProvider.getTestDirectory().createDir("sshd/files")
+        configDir = testDirectoryProvider.getTestDirectory().createDir("sshd/config")
 
         sshd = setupConfiguredTestSshd();
         sshd.start();
diff --git a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/executer/OutputScraperTest.groovy b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/executer/OutputScraperTest.groovy
new file mode 100644
index 0000000..a5e01f7
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/executer/OutputScraperTest.groovy
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.executer
+
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 12/21/12
+ */
+class OutputScraperTest extends Specification {
+
+    def output = """Included projects: [root project 'unknown-test-122', project ':api', project ':impl', project ':util']
+Evaluating root project 'unknown-test-122' using build file '/Users/szczepan/gradle/gradle.src/build/tmp/test files/AbstractIntegrationSpec/unknown-test-122/build.gradle'.
+Evaluating project ':api' using empty build file
+Selected primary task ':api:build'
+Evaluating project ':foo:bar' using build file '/Users/szczepan/gradle/gradle.src/build/tmp/test files/AbstractIntegrationSpec/unknown-test-122/api/build.gradle'.
+Selected primary task ':foo:bar:build'
+All projects evaluated.
+Tasks to be executed: ...
+"""
+
+    def "finds evaluated projects"() {
+        expect:
+        new OutputScraper("").evaluatedProjects == []
+        new OutputScraper(output).evaluatedProjects == [':', ':api', ':foo:bar']
+    }
+
+    def "sucessfully asserts projects evaluated"() {
+        expect:
+        new OutputScraper("").assertProjectsEvaluated([])
+        new OutputScraper(output).assertProjectsEvaluated([':', ':api', ':foo:bar'])
+    }
+
+    def "asserts projects evaluated"() {
+        when:
+        new OutputScraper(output).assertProjectsEvaluated(expected)
+        then:
+        thrown(AssertionError)
+
+        where:
+        expected << [
+            [':', ':api', ':foo:bar', 'extra'], //extra
+            ['extra', ':', ':api', ':foo:bar'], //extra
+            [':api', ':', ':foo:bar'],    //order
+            ['root', ':api', ':foo:bar'], //no match
+            []
+        ]
+    }
+
+    def "provides decent exception when no evaluated projects found"() {
+        when:
+        new OutputScraper("").assertProjectsEvaluated([":"])
+        then:
+        def ex = thrown(AssertionError)
+        ex.message == OutputScraper.EVAL_NOT_FOUND
+    }
+
+    def "all evaluated hook must be fired after all projects evaluated"() {
+        def output = """...
+Evaluating project ':foo' using build file...
+All projects evaluated.
+Evaluating project ':api' using build file...
+...
+"""
+
+        when:
+        new OutputScraper(output).assertProjectsEvaluated([":foo", ":api"])
+        then:
+        def ex = thrown(AssertionError)
+        ex.message.contains OutputScraper.ALL_EVAL_ERROR
+    }
+
+    def "all evaluated hook must be fired after root project evaluated"() {
+        def output = """...
+All projects evaluated.
+Evaluating root project 'foo' using build file...
+...
+"""
+
+        when:
+        new OutputScraper(output).assertProjectsEvaluated([":"])
+        then:
+        def ex = thrown(AssertionError)
+        ex.message.contains OutputScraper.ALL_EVAL_ERROR
+    }
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/IsTestableGradleVersionSpecTest.groovy b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/IsTestableGradleVersionSpecTest.groovy
new file mode 100644
index 0000000..f319c96
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/IsTestableGradleVersionSpecTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.versions
+
+import spock.lang.Specification
+
+import static org.gradle.integtests.fixtures.versions.ReleasedGradleVersion.Type.*
+import static org.gradle.util.GradleVersion.current
+import static org.gradle.util.GradleVersion.version
+
+class IsTestableGradleVersionSpecTest extends Specification {
+
+    boolean isSatisfied(ReleasedGradleVersion releasedGradleVersion) {
+        new IsTestableGradleVersionSpec().isSatisfiedBy(releasedGradleVersion)
+    }
+
+    def "release candidates"() {
+        expect:
+        !isSatisfied(new ReleasedGradleVersion(version("1.0-rc-1"), RELEASE_CANDIDATE, false))
+        isSatisfied(new ReleasedGradleVersion(version("1.0-rc-1"), RELEASE_CANDIDATE, true))
+    }
+
+    def "excludes old versions"() {
+        expect:
+        !isSatisfied(new ReleasedGradleVersion(version("0.7"), FINAL, true))
+    }
+
+    def "excludes bad versions"() {
+        expect:
+        !isSatisfied(new ReleasedGradleVersion(version("0.9-rc-1"), FINAL, true))
+        !isSatisfied(new ReleasedGradleVersion(version("0.9-rc-2"), FINAL, true))
+        !isSatisfied(new ReleasedGradleVersion(version("1.0-milestone-4"), FINAL, true))
+    }
+
+    def "excludes current version"() {
+        expect:
+        !isSatisfied(new ReleasedGradleVersion(current(), FINAL, true))
+    }
+
+    def "does not accept nightly"() {
+        expect:
+        !isSatisfied(new ReleasedGradleVersion(current(), NIGHTLY, true))
+    }
+
+}
diff --git a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/ReleasedVersionDistributionsTest.groovy b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/ReleasedVersionDistributionsTest.groovy
new file mode 100644
index 0000000..ed7ff71
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/ReleasedVersionDistributionsTest.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.versions
+
+import org.gradle.internal.Factory
+import spock.lang.Specification
+
+import static org.gradle.integtests.fixtures.versions.ReleasedGradleVersion.Type.FINAL
+import static org.gradle.integtests.fixtures.versions.ReleasedGradleVersion.Type.RELEASE_CANDIDATE
+import static org.gradle.util.GradleVersion.version
+
+class ReleasedVersionDistributionsTest extends Specification {
+
+    List<ReleasedGradleVersion> all = []
+
+    def versions() {
+        new ReleasedVersionDistributions(new Factory<List<ReleasedGradleVersion>>() {
+            List<ReleasedGradleVersion> create() {
+                all
+            }
+        })
+    }
+
+    // Will fail if the classpath resource is not available, see ClasspathVersionJsonSource
+    def "can create from classpath"() {
+        when:
+        def versions = new ReleasedVersionDistributions()
+
+        then:
+        !versions.all.empty
+        versions.all*.version == versions.all*.version.sort().reverse()
+    }
+
+    def "get most recent final does that"() {
+        when:
+        all << new ReleasedGradleVersion(version("1.3-rc-1"), RELEASE_CANDIDATE, true)
+        all << new ReleasedGradleVersion(version("1.2"), FINAL, true)
+
+        then:
+        versions().mostRecentFinalRelease.version == version("1.2")
+    }
+
+    def "get all final does that"() {
+        when:
+        all << new ReleasedGradleVersion(version("1.3-rc-1"), RELEASE_CANDIDATE, true)
+        all << new ReleasedGradleVersion(version("1.2"), FINAL, true)
+
+        then:
+        versions().all*.version == [version("1.3-rc-1"), version("1.2")]
+    }
+
+}
diff --git a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/VersionWebServiceJsonParserTest.groovy b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/VersionWebServiceJsonParserTest.groovy
new file mode 100644
index 0000000..e8938d9
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/VersionWebServiceJsonParserTest.groovy
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.versions
+
+import spock.lang.Specification
+
+import static org.gradle.integtests.fixtures.versions.ReleasedGradleVersion.Type.*
+
+class VersionWebServiceJsonParserTest extends Specification {
+
+    def "can read"() {
+        when:
+        def versions = parse('''[{
+	"version":"1.4-20130107230014+0000",
+	"buildTime":"20130107230014+0000",
+	"current":false,
+	"snapshot":true,
+	"nightly":true,
+	"activeRc":false,
+	"rcFor":"",
+	"broken":false,
+	"downloadUrl":"http:\\/\\/services.gradle.org\\/distributions-snapshots\\/gradle-1.4-20130107230014+0000-bin.zip"
+},{
+	"version":"1.3-rc-1",
+	"buildTime":"20121120113738+0000",
+	"current":false,
+	"snapshot":false,
+	"nightly":false,
+	"activeRc":true,
+	"rcFor":"1.3",
+	"broken":false,
+	"downloadUrl":"http:\\/\\/services.gradle.org\\/distributions\\/gradle-1.3-rc-1-bin.zip"
+},{
+	"version":"1.2",
+	"buildTime":"20121115155343+0000",
+	"current":true,
+	"snapshot":false,
+	"nightly":false,
+	"activeRc":false,
+	"rcFor":"",
+	"broken":false,
+	"downloadUrl":"http:\\/\\/services.gradle.org\\/distributions\\/gradle-1.2-bin.zip"
+},{
+	"version":"1.1",
+	"buildTime":"20120731132432+0000",
+	"current":false,
+	"snapshot":false,
+	"nightly":false,
+	"activeRc":false,
+	"rcFor":"",
+	"broken":false,
+	"downloadUrl":"http:\\/\\/services.gradle.org\\/distributions\\/gradle-1.1-bin.zip"
+},{
+	"version":"1.1-rc-2",
+	"buildTime":"20120726075103+0000",
+	"current":false,
+	"snapshot":false,
+	"nightly":false,
+	"activeRc":false,
+	"rcFor":"1.1",
+	"broken":false,
+	"downloadUrl":"http:\\/\\/services.gradle.org\\/distributions\\/gradle-1.1-rc-2-bin.zip"
+},{
+	"version":"1.1-rc-1",
+	"buildTime":"20120724134404+0000",
+	"current":false,
+	"snapshot":false,
+	"nightly":false,
+	"activeRc":false,
+	"rcFor":"1.1",
+	"broken":false,
+	"downloadUrl":"http:\\/\\/services.gradle.org\\/distributions\\/gradle-1.1-rc-1-bin.zip"
+},{
+	"version":"1.0",
+	"buildTime":"20120612025621+0200",
+	"current":false,
+	"snapshot":false,
+	"nightly":false,
+	"activeRc":false,
+	"rcFor":"",
+	"broken":false,
+	"downloadUrl":"http:\\/\\/services.gradle.org\\/distributions\\/gradle-1.0-bin.zip"
+}]''')
+
+        then:
+        versions.size() == 7
+        versions.findAll { it.type == FINAL }.size() == 3
+        versions.findAll { it.type == RELEASE_CANDIDATE }.size() == 3
+        versions.find { it.type == RELEASE_CANDIDATE && it.current }.version.version == "1.3-rc-1"
+        versions.find { it.type == FINAL && it.current }.version.version == "1.2"
+        versions.find { it.type == NIGHTLY }.current
+    }
+
+    def parse(String text) {
+        return new VersionWebServiceJsonParser(new org.gradle.internal.Factory<Reader>() {
+            public Reader create() {
+                return new StringReader(text);
+            }
+        }).create();
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/VersionsInfoTest.groovy b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/VersionsInfoTest.groovy
deleted file mode 100644
index 12edfbe..0000000
--- a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/VersionsInfoTest.groovy
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures.versions
-
-import org.gradle.util.GradleVersion
-import spock.lang.Specification
-import static org.gradle.util.GradleVersion.version
-
-/**
- * by Szczepan Faber, created at: 3/12/12
- */
-class VersionsInfoTest extends Specification {
-
-    def info = new VersionsInfo()
-
-    def "reads versions information from the actual resource"() {
-        expect:
-        !info.versions.empty
-        info.versions.size() > 5
-        info.versions.find { "1.0-milestone-3"  }
-        info.versions.find { "1.0-milestone-8a" }
-    }
-
-    def "versions are ordered latest first"() {
-        expect:
-        version(info.versions[0]) > version(info.versions[1])
-        info.versions == info.versions.sort( { a,b -> version(b).compareTo(version(a)) })
-    }
-    
-    def "contains release candidate if not yet released"() {
-        when:
-        info.getVersionsJson = {[
-            [version: "1.0-milestone-8a", current:true, nightly:false, rcFor:null],
-            [version: "1.0-milestone-9-20120309103546+0100", current:false, nightly:false, rcFor:"1.0-milestone-9"],
-            [version: "1.0-rc-1-20120312000043+0100", current:false, nightly:true, rcFor:null],
-            [version: "1.0-milestone-8", current:false, nightly:false, rcFor:null],
-            [version: "1.0-milestone-7", current:false, nightly:false, rcFor:null]
-        ]}
-
-        then:
-        info.versions == ["1.0-milestone-9-20120309103546+0100", "1.0-milestone-8a", "1.0-milestone-8", "1.0-milestone-7"]
-    }
-
-    def "excludes release candidate if already released"() {
-        when:
-        info.getVersionsJson = {[
-            [version: "1.0-milestone-9", current:true, nightly:false, rcFor:null],
-            [version: "1.0-milestone-9-20120309103546+0100", current:false, nightly:false, rcFor:"1.0-milestone-9"],
-            [version: "1.0-rc-1-20120312000043+0100", current:false, nightly:true, rcFor:null],
-            [version: "1.0-milestone-8", current:false, nightly:false, rcFor:null],
-            [version: "1.0-milestone-7", current:false, nightly:false, rcFor:null]
-        ]}
-
-        then:
-        info.versions == ["1.0-milestone-9", "1.0-milestone-8", "1.0-milestone-7"]
-    }
-
-    def "excludes certain versions"() {
-        given:
-        info.lowestInterestingVersion = "1.0-milestone-7"
-        info.excludedVersions = ["1.0-milestone-8"]
-
-        when:
-        info.getVersionsJson = {[
-            [version: "1.0-milestone-9", current:true, nightly:false, rcFor:null],
-            [version: "1.0-milestone-8", current:false, nightly:false, rcFor:null],
-            [version: "1.0-milestone-7", current:false, nightly:false, rcFor:null],
-            [version: "1.0-milestone-6", current:false, nightly:false, rcFor:null],
-            [version: "1.0-milestone-5", current:false, nightly:false, rcFor:null]
-        ]}
-
-        then:
-        info.versions == ["1.0-milestone-9", "1.0-milestone-7"]
-    }
-
-    def "excludes current version"() {
-        when:
-        info.getVersionsJson = {[
-                [version: GradleVersion.current().version, current:false, nightly:false, rcFor:null]
-        ]}
-
-        then:
-        info.versions == []
-    }
-}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/DefaultTestExecutionResult.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/DefaultTestExecutionResult.groovy
new file mode 100644
index 0000000..3201217
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/DefaultTestExecutionResult.groovy
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures
+
+import groovy.util.slurpersupport.GPathResult
+import org.gradle.test.fixtures.file.TestFile
+import org.hamcrest.Matcher
+
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.assertThat
+
+class DefaultTestExecutionResult implements TestExecutionResult {
+    private final TestFile buildDir
+
+    def DefaultTestExecutionResult(TestFile projectDir, String buildDirName = 'build') {
+        this.buildDir = projectDir.file(buildDirName)
+    }
+
+    boolean hasJUnitXmlResults() {
+        xmlResultsDir().list().length > 0
+    }
+
+    TestExecutionResult assertTestClassesExecuted(String... testClasses) {
+        Map<String, File> classes = findClasses()
+        assertThat(classes.keySet(), equalTo(testClasses as Set));
+        this
+    }
+
+    TestClassExecutionResult testClass(String testClass) {
+        return new JUnitTestClassExecutionResult(findTestClass(testClass), testClass)
+    }
+
+    private def findTestClass(String testClass) {
+        def classes = findClasses()
+        assertThat(classes.keySet(), hasItem(testClass))
+        def classFile = classes.get(testClass)
+        assertThat(classFile, notNullValue())
+        return new XmlSlurper().parse(classFile)
+    }
+
+    private def findClasses() {
+        xmlResultsDir().assertIsDir()
+
+        Map<String, File> classes = [:]
+        buildDir.file('test-results').eachFile { File file ->
+            def matcher = (file.name =~ /TEST-(.+)\.xml/)
+            if (matcher.matches()) {
+                classes[matcher.group(1)] = file
+            }
+        }
+        return classes
+    }
+
+    private TestFile xmlResultsDir() {
+        buildDir.file('test-results')
+    }
+}
+
+class JUnitTestClassExecutionResult implements TestClassExecutionResult {
+    GPathResult testClassNode
+    String testClassName
+    boolean checked
+
+    def JUnitTestClassExecutionResult(GPathResult testClassNode, String testClassName) {
+        this.testClassNode = testClassNode
+        this.testClassName = testClassName
+    }
+
+    def JUnitTestClassExecutionResult(String content, String testClassName) {
+        this(new XmlSlurper().parse(new StringReader(content)), testClassName)
+    }
+
+    TestClassExecutionResult assertTestsExecuted(String... testNames) {
+        Map<String, Node> testMethods = findTests()
+        assertThat(testMethods.keySet(), equalTo(testNames as Set))
+        this
+    }
+
+    TestClassExecutionResult assertTestCount(int tests, int failures, int errors) {
+        assert testClassNode. at tests == tests
+        assert testClassNode. at failures == failures
+        assert testClassNode. at errors == errors
+        this
+    }
+
+    TestClassExecutionResult assertTestPassed(String name) {
+        Map<String, Node> testMethods = findTests()
+        assertThat(testMethods.keySet(), hasItem(name))
+        assertThat(testMethods[name].failure.size(), equalTo(0))
+        this
+    }
+
+    TestClassExecutionResult assertTestFailed(String name, Matcher<? super String>... messageMatchers) {
+        Map<String, Node> testMethods = findTests()
+        assertThat(testMethods.keySet(), hasItem(name))
+
+        def failures = testMethods[name].failure
+        assertThat("Expected ${messageMatchers.length} failures. Found: $failures", failures.size(), equalTo(messageMatchers.length))
+
+        for (int i = 0; i < messageMatchers.length; i++) {
+            assertThat(failures[i]. at message.text(), messageMatchers[i])
+        }
+        this
+    }
+
+    TestClassExecutionResult assertTestSkipped(String name) {
+        throw new UnsupportedOperationException()
+    }
+
+    TestClassExecutionResult assertTestsSkipped(String... testNames) {
+        Map<String, Node> testMethods = findIgnoredTests()
+        assertThat(testMethods.keySet(), equalTo(testNames as Set))
+        this
+    }
+
+    TestClassExecutionResult assertConfigMethodPassed(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    TestClassExecutionResult assertConfigMethodFailed(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    TestClassExecutionResult assertStdout(Matcher<? super String> matcher) {
+        def stdout = testClassNode.'system-out'[0].text();
+        assertThat(stdout, matcher)
+        this
+    }
+
+    TestClassExecutionResult assertStderr(Matcher<? super String> matcher) {
+        def stderr = testClassNode.'system-err'[0].text();
+        assertThat(stderr, matcher)
+        this
+    }
+
+    private def findTests() {
+        if (!checked) {
+            assertThat(testClassNode.name(), equalTo('testsuite'))
+            assertThat(testClassNode. at name.text(), equalTo(testClassName))
+            assertThat(testClassNode. at tests.text(), not(equalTo('')))
+            assertThat(testClassNode. at failures.text(), not(equalTo('')))
+            assertThat(testClassNode. at errors.text(), not(equalTo('')))
+            assertThat(testClassNode. at time.text(), not(equalTo('')))
+            assertThat(testClassNode. at timestamp.text(), not(equalTo('')))
+            assertThat(testClassNode. at hostname.text(), not(equalTo('')))
+            assertThat(testClassNode.properties.size(), equalTo(1))
+            testClassNode.testcase.each { node ->
+                assertThat(node. at classname.text(), equalTo(testClassName))
+                assertThat(node. at name.text(), not(equalTo('')))
+                assertThat(node. at time.text(), not(equalTo('')))
+                node.failure.each { failure ->
+                    assertThat(failure. at message.size(), equalTo(1))
+                    assertThat(failure. at type.text(), not(equalTo('')))
+                    assertThat(failure.text(), not(equalTo('')))
+                }
+            }
+            assertThat(testClassNode.'system-out'.size(), equalTo(1))
+            assertThat(testClassNode.'system-err'.size(), equalTo(1))
+            checked = true
+        }
+        Map testMethods = [:]
+        testClassNode.testcase.each { testMethods[it. at name.text()] = it }
+        return testMethods
+    }
+
+    private def findIgnoredTests() {
+        Map testMethods = [:]
+        testClassNode."ignored-testcase".each { testMethods[it. at name.text()] = it }
+        return testMethods
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java
similarity index 100%
rename from subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java
rename to subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/TestExecutionResult.java b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/TestExecutionResult.java
new file mode 100755
index 0000000..cdd893d
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/TestExecutionResult.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures;
+
+public interface TestExecutionResult {
+    /**
+     * Asserts that the given test classes (and only the given test classes) were executed.
+     */
+    TestExecutionResult assertTestClassesExecuted(String... testClasses);
+
+    /**
+     * Returns the result for the given test class.
+     */
+    TestClassExecutionResult testClass(String testClass);
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/BlockTarget.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/BlockTarget.groovy
new file mode 100644
index 0000000..7d11d67
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/BlockTarget.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.concurrent
+
+/**
+ * A target for blocking. Exposes each named instant as a property, and accessing that property will block until the
+ * instant has been reached.
+ */
+class BlockTarget {
+    private final Instants instants
+
+    BlockTarget(Instants instants) {
+        this.instants = instants
+    }
+
+    def getProperty(String name) {
+        instants.waitFor(name)
+        return null
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/ConcurrentSpec.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/ConcurrentSpec.groovy
new file mode 100644
index 0000000..88b9522
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/ConcurrentSpec.groovy
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.concurrent
+
+import org.gradle.internal.concurrent.ExecutorFactory
+import spock.lang.Specification
+import spock.lang.Timeout
+
+import java.util.concurrent.Executor
+
+/**
+ * A specification that uses multiple test threads. Provides an {@link Executor} and {@link org.gradle.internal.concurrent.ExecutorFactory} implementation.
+ *
+ * <p>This class maintains a set of <em>instants</em> reached by the test. An instant records the point in time that a test thread reached a certain point of its execution.
+ * Once the test threads have completed, you can make assertions about the ordering of the various instants relative to each other. You can also block until a given
+ * instant has been reached.
+ *
+ * <p>The main test thread cannot define instants, except within a {@link #async} block.
+ *
+ * <p>NOTE: Be careful when using this class with Spock mock objects, as these mocks perform some synchronisation of their own. This means that you may not be testing
+ * what you think you are testing.
+ */
+ at Timeout(60)
+class ConcurrentSpec extends Specification {
+    /**
+     * An object that allows instants to be defined and queried.
+     *
+     * @see NamedInstant
+     */
+    final Instants instant = new Instants()
+
+    /**
+     * An object that allows operations to be defined and queried.
+     *
+     * @see NamedOperation
+     */
+    final Operations operation = new Operations(instant, instant)
+
+    /**
+     * An object that allows control over the current thread.
+     */
+    final TestThread thread = new TestThread(instant)
+
+    private final TestExecutor executor = new TestExecutor(instant)
+    private final TestExecutorFactory executorFactory = new TestExecutorFactory(executor)
+
+    /**
+     * Returns an Executor that should be used for running asynchronous actions.
+     */
+    Executor getExecutor() {
+        return executor
+    }
+
+    /**
+     * Returns an ExecutorFactory that should be used for running asynchronous actions.
+     */
+    ExecutorFactory getExecutorFactory() {
+        return executorFactory
+    }
+
+    def cleanup() {
+        executor.stop(new Date(System.currentTimeMillis() + 5000))
+    }
+
+    /**
+     * Executes the given action and then blocks until all test threads have completed. The action may define instants for later querying outside the block.
+     */
+    void async(Runnable action) {
+        Date timeout = new Date(System.currentTimeMillis() + 20000)
+        executor.start()
+        try {
+            executor.execute(action)
+        } finally {
+            executor.stop(timeout)
+        }
+    }
+
+    /**
+     * Starts a test thread that will run the given action. The action may define instants for later querying.
+     */
+    void start(Runnable action) {
+        executor.execute(action)
+    }
+
+    /**
+     * Returns a range that contains the given milliseconds +/- some error margin
+     */
+    Range approx(long millis) {
+        return new Range(millis)
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Duration.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Duration.groovy
new file mode 100644
index 0000000..e1287f6
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Duration.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.concurrent
+
+class Duration {
+    private final long nanos
+
+    Duration(long nanos) {
+        this.nanos = nanos
+    }
+
+    long getMillis() {
+        return nanos / 1000000
+    }
+
+    @Override
+    String toString() {
+        return "[$nanos nanos]"
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Instant.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Instant.groovy
new file mode 100644
index 0000000..7a8e894
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Instant.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.concurrent
+
+/**
+ * A point in time.
+ */
+class Instant implements Comparable<Instant> {
+    final long nanos
+
+    Instant(long nanos) {
+        this.nanos = nanos
+    }
+
+    @Override
+    String toString() {
+        return "[instant at $nanos]"
+    }
+
+    int compareTo(Instant t) {
+        return nanos.compareTo(t.nanos)
+    }
+
+    Instant plus(long millis) {
+        return new Instant(nanos + millis * 1000)
+    }
+
+    Duration minus(Instant t) {
+        return new Duration(nanos - t.nanos)
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/InstantFactory.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/InstantFactory.groovy
new file mode 100644
index 0000000..4df4faa
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/InstantFactory.groovy
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.concurrent
+
+interface InstantFactory {
+    NamedInstant now(String name)
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Instants.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Instants.groovy
new file mode 100644
index 0000000..6edda6c
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Instants.groovy
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.concurrent
+
+/**
+ * A dynamic collection of {@link NamedInstant} objects. When a property of this object is accessed from a test thread, a new instant is defined. When
+ * accessed from the main thread, queries an existing instant, asserting that it exists.
+ */
+class Instants implements InstantFactory, TestThreadListener, OperationListener {
+    private final Object lock = new Object()
+    private final Map<String, NamedInstant> timePoints = [:]
+    private final Set<Thread> testThreads = new HashSet<Thread>()
+    private int operations
+
+    @Override
+    String toString() {
+        return "instants"
+    }
+
+    void operationStarted() {
+        synchronized (lock) {
+            operations++
+        }
+    }
+
+    void operationFinished() {
+        synchronized (lock) {
+            operations--
+            lock.notifyAll()
+        }
+    }
+
+    void threadStarted(Thread thread) {
+        synchronized (lock) {
+            testThreads.add(thread)
+        }
+    }
+
+    void threadFinished(Thread thread) {
+        synchronized (lock) {
+            testThreads.remove(thread)
+            lock.notifyAll()
+        }
+    }
+
+    void waitFor(String name) {
+        long expiry = System.currentTimeMillis() + 12000;
+        synchronized (lock) {
+            while (!timePoints.containsKey(name) && System.currentTimeMillis() < expiry) {
+                println "* ${Thread.currentThread().name} waiting for instant '$name' ..."
+                if (testThreads.empty && operations == 0) {
+                    throw new IllegalStateException("Cannot wait for instant '$name', as it has not been defined and no test threads are currently running.")
+                }
+                if (testThreads.size() == 1 && testThreads.contains(Thread.currentThread()) && operations == 0) {
+                    throw new IllegalStateException("Cannot wait for instant '$name', as it has not been defined and no other test threads are currently running.")
+                }
+                lock.wait(expiry - System.currentTimeMillis())
+            }
+            if (timePoints.containsKey(name)) {
+                return
+            }
+            throw new IllegalStateException("Timeout waiting for instant '$name' to be defined by another test thread.")
+        }
+    }
+
+    def getProperty(String name) {
+        synchronized (lock) {
+            def testThread = testThreads.contains(Thread.currentThread())
+            if (testThread) {
+                return now(name)
+            } else {
+                return get(name)
+            }
+        }
+    }
+
+    NamedInstant get(String name) {
+        synchronized (lock) {
+            def time = timePoints[name]
+            if (time == null) {
+                throw new IllegalStateException("Instant '$name' has not been defined by any test thread.")
+            }
+            return time
+        }
+    }
+
+    NamedInstant now(String name) {
+        synchronized (lock) {
+            def time = timePoints[name]
+            if (time != null) {
+                throw new IllegalStateException("Instant '$name' has already been defined by another test thread.")
+            }
+            def now = System.nanoTime()
+            time = new NamedInstant(name, now, timePoints.size())
+            timePoints[name] = time
+            lock.notifyAll()
+            println "* ${Thread.currentThread().name} instant $name reached"
+            return time
+        }
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/NamedInstant.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/NamedInstant.groovy
new file mode 100644
index 0000000..53eb2fd
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/NamedInstant.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.concurrent
+
+/**
+ * A point in time with an associated name.
+ */
+class NamedInstant extends Instant {
+    private final String name
+    private final int sequenceNumber
+
+    NamedInstant(String name, long time, int sequenceNumber) {
+        super(time)
+        this.name = name
+        this.sequenceNumber = sequenceNumber
+    }
+
+    @Override
+    String toString() {
+        return "[instant ${name} #${sequenceNumber} at ${nanos}]"
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/NamedOperation.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/NamedOperation.groovy
new file mode 100644
index 0000000..3c72693
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/NamedOperation.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.concurrent
+
+import java.util.concurrent.atomic.AtomicReference
+
+/**
+ * Represents some operation performed by a test. Has a start and end instant.
+ */
+class NamedOperation {
+    final String name
+    final Instant start
+    private final AtomicReference<Instant> end = new AtomicReference<>()
+
+    NamedOperation(String name) {
+        this.name = name
+        this.start = new Instant(System.nanoTime())
+    }
+
+    @Override
+    String toString() {
+        return "[operation $name]"
+    }
+
+    Instant getEnd() {
+        def instant = end.get()
+        if (instant == null) {
+            throw new IllegalStateException("Operation '$name' has not completed yet.")
+        }
+        return instant
+    }
+
+    Duration getDuration() {
+        return getEnd() - start
+    }
+
+    void completed(Instant when) {
+        end.compareAndSet(null, when)
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/OperationListener.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/OperationListener.groovy
new file mode 100644
index 0000000..f7ceca0
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/OperationListener.groovy
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.concurrent
+
+interface OperationListener {
+    void operationStarted()
+
+    void operationFinished()
+}
\ No newline at end of file
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Operations.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Operations.groovy
new file mode 100644
index 0000000..8f3fcef
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Operations.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.concurrent
+
+/**
+ * A dynamic collection of {@link NamedOperation} instances. When a method is called that takes a single Runnable as parameter, a new operation is defined.
+ * When a property is accessed, queries an existing operation, asserting that it exists. For example:
+ *
+ * <pre>
+ *  when:
+ *  // runs the given closure and defines operation 'doStuff'
+ *  operation.doStuff {
+ *      // do some test action
+ *  }
+ *
+ *  then:
+ *  // query
+ *  operation.doStuff.end > operation.doStuff.start
+ *  instant.doStuff == operation.doStuff.end
+ * </pre>
+ */
+class Operations {
+    private final InstantFactory instantFactory
+    private final Object lock = new Object()
+    private final Map<String, NamedOperation> operations = [:]
+    private final OperationListener listener
+
+    Operations(InstantFactory instantFactory, OperationListener listener) {
+        this.listener = listener
+        this.instantFactory = instantFactory
+    }
+
+    public def propertyMissing(String name) {
+        synchronized (lock) {
+            if (!operations.containsKey(name)) {
+                throw new IllegalStateException("Operation '$name' has not been defined by any test thread.")
+            }
+            return operations[name]
+        }
+    }
+
+    public def methodMissing(String name, def args) {
+        if (args.length != 1 || !(args[0] instanceof Runnable)) {
+            throw new MissingMethodException(name, getClass(), args)
+        }
+        def action = args[0]
+        def operation
+        synchronized (lock) {
+            if (operations.containsKey(name)) {
+                throw new IllegalStateException("Operation '$name' has already been defined.")
+            }
+            operation = new NamedOperation(name)
+            operations[name] = operation
+        }
+        listener.operationStarted()
+        try {
+            action.run()
+        } finally {
+            operation.completed(instantFactory.now(operation.name))
+            listener.operationFinished()
+        }
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Range.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Range.groovy
new file mode 100644
index 0000000..aaf8409
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/Range.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.concurrent
+
+/**
+ * A range of time.
+ */
+class Range {
+    private final long millis
+
+    Range(long millis) {
+        this.millis = millis
+    }
+
+    @Override
+    String toString() {
+        return "[approx $millis millis]"
+    }
+
+    boolean isCase(Duration duration) {
+        def actualMillis = duration.millis
+        return actualMillis > millis - 500 && actualMillis < millis + 2000
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestExecutor.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestExecutor.groovy
new file mode 100644
index 0000000..9bd2338
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestExecutor.groovy
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.concurrent
+
+import java.util.concurrent.Executor
+import java.util.concurrent.locks.Condition
+import java.util.concurrent.locks.Lock
+import java.util.concurrent.locks.ReentrantLock
+
+class TestExecutor implements Executor {
+    private final Lock lock = new ReentrantLock()
+    private final Condition condition = lock.newCondition()
+    private final Set<Thread> threads = new HashSet<Thread>()
+    private final TestThreadListener listener
+    private Thread owner
+    private Throwable failure
+    private int threadNum
+
+    TestExecutor(TestThreadListener listener) {
+        this.listener = listener
+    }
+
+    void start() {
+        lock.lock()
+        try {
+            if (owner != null) {
+                throw new IllegalStateException("Cannot nest async { } blocks.")
+            }
+            owner = Thread.currentThread()
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    void execute(Runnable runnable) {
+        def thread = new Thread() {
+            @Override
+            void run() {
+                try {
+                    println "* ${Thread.currentThread().name} running"
+                    runnable.run()
+                } catch (Throwable throwable) {
+                    println "* ${Thread.currentThread().name} failed"
+                    lock.lock()
+                    try {
+                        if (failure == null) {
+                            failure = throwable
+                        }
+                    } finally {
+                        lock.unlock()
+                    }
+                } finally {
+                    println "* ${Thread.currentThread().name} finished"
+                    listener.threadFinished(Thread.currentThread())
+                    lock.lock()
+                    try {
+                        threads.remove(Thread.currentThread())
+                        condition.signalAll()
+                    } finally {
+                        lock.unlock()
+                    }
+                }
+            }
+        }
+
+        lock.lock()
+        try {
+            threads << thread
+            thread.name = "Test thread ${++threadNum}"
+        } finally {
+            lock.unlock()
+        }
+
+        listener.threadStarted(thread)
+        thread.start()
+    }
+
+    void stop(Date expiry) {
+        lock.lock()
+        try {
+            if (!threads.isEmpty()) {
+                println "* waiting for ${threads.size()} test threads to complete."
+            }
+
+            while (!threads.isEmpty()) {
+                if (!condition.awaitUntil(expiry)) {
+                    break;
+                }
+            }
+
+            if (!threads.isEmpty()) {
+                println "* timeout waiting for ${threads.size()} threads to complete"
+                threads.each { thread ->
+                    IllegalStateException e = new IllegalStateException("Timeout waiting for ${thread.name} to complete.")
+                    e.stackTrace = thread.stackTrace
+                    if (failure == null) {
+                        failure = e
+                    } else {
+                        e.printStackTrace()
+                    }
+                    thread.interrupt()
+                }
+            }
+            threads.clear()
+            if (failure != null) {
+                throw failure
+            }
+        } finally {
+            owner = null
+            failure = null
+            lock.unlock()
+        }
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestExecutorFactory.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestExecutorFactory.groovy
new file mode 100644
index 0000000..83cf08b
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestExecutorFactory.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.concurrent
+
+import org.gradle.internal.concurrent.ExecutorFactory
+import org.gradle.internal.concurrent.StoppableExecutor
+
+class TestExecutorFactory implements ExecutorFactory {
+    private final TestExecutor executor
+
+    TestExecutorFactory(TestExecutor executor) {
+        this.executor = executor
+    }
+
+    StoppableExecutor create(String displayName) {
+        return new TestStoppableExecutor(executor)
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestStoppableExecutor.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestStoppableExecutor.groovy
new file mode 100644
index 0000000..39e75ec
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestStoppableExecutor.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.concurrent
+
+import org.gradle.internal.concurrent.StoppableExecutor
+
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.locks.Condition
+import java.util.concurrent.locks.Lock
+import java.util.concurrent.locks.ReentrantLock
+
+class TestStoppableExecutor implements StoppableExecutor {
+    private final Lock lock = new ReentrantLock()
+    private final Condition condition = lock.newCondition()
+    private int count
+    private final TestExecutor executor;
+
+    TestStoppableExecutor(TestExecutor executor) {
+        this.executor = executor
+    }
+
+    void execute(Runnable command) {
+        lock.lock()
+        try {
+            count++
+        } finally {
+            lock.unlock()
+        }
+
+        executor.execute {
+            try {
+                command.run()
+            } finally {
+                lock.lock()
+                try {
+                    count--
+                    condition.signalAll()
+                } finally {
+                    lock.unlock()
+                }
+            }
+        }
+    }
+
+    void requestStop() {
+    }
+
+    void stop() {
+        lock.lock()
+        try {
+            while (count > 0) {
+                condition.await()
+            }
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    void stop(int timeoutValue, TimeUnit timeoutUnits) throws IllegalStateException {
+        throw new UnsupportedOperationException()
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestThread.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestThread.groovy
new file mode 100644
index 0000000..03cc24b
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestThread.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.concurrent
+
+/**
+ * Represents the current thread.
+ */
+class TestThread {
+    private final Instants instants
+
+    TestThread(Instants instants) {
+        this.instants = instants
+    }
+
+    void block() {
+        Thread.sleep(750)
+    }
+
+    BlockTarget getBlockUntil() {
+        return new BlockTarget(instants)
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestThreadListener.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestThreadListener.groovy
new file mode 100644
index 0000000..8b288c1
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/concurrent/TestThreadListener.groovy
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.concurrent
+
+interface TestThreadListener {
+    void threadStarted(Thread thread)
+    void threadFinished(Thread thread)
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestDirectoryProvider.java b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestDirectoryProvider.java
new file mode 100644
index 0000000..ad91088
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestDirectoryProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.file;
+
+/**
+ * Implementations provide a working space to be used in tests.
+ *
+ * The client is not responsible for removing any files.
+ */
+public interface TestDirectoryProvider {
+
+    /**
+     * The directory to use, guaranteed to exist.
+     *
+     * @return The directory to use, guaranteed to exist.
+     */
+    TestFile getTestDirectory();
+
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestDirectoryProviderFinder.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestDirectoryProviderFinder.groovy
new file mode 100644
index 0000000..204b76c
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestDirectoryProviderFinder.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.test.fixtures.file
+
+import org.gradle.util.RuleHelper
+
+class TestDirectoryProviderFinder {
+
+    @SuppressWarnings("GrMethodMayBeStatic")
+    TestDirectoryProvider findFor(Object test) {
+        if (test instanceof TestDirectoryProvider) {
+            test
+        } else {
+            TestDirectoryProvider testDirectoryProvider = RuleHelper.findField(test, TestDirectoryProvider)
+            if (testDirectoryProvider == null) {
+                throw new IllegalStateException("Test ${test.class.name} does not implement TestDirectoryProvider and has no field of that type")
+            }
+            testDirectoryProvider
+        }
+    }
+
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestFile.java b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestFile.java
new file mode 100644
index 0000000..13c3b34
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestFile.java
@@ -0,0 +1,555 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.file;
+
+import groovy.lang.Closure;
+import org.apache.commons.io.FileUtils;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.taskdefs.Tar;
+import org.apache.tools.ant.taskdefs.Zip;
+import org.apache.tools.ant.types.EnumeratedAttribute;
+import org.codehaus.groovy.runtime.DefaultGroovyMethods;
+import org.hamcrest.Matcher;
+
+import java.io.*;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.util.*;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+import static org.junit.Assert.*;
+
+public class TestFile extends File {
+    private boolean useNativeTools;
+
+    public TestFile(File file, Object... path) {
+        super(join(file, path).getAbsolutePath());
+    }
+
+    public TestFile(URI uri) {
+        this(new File(uri));
+    }
+
+    public TestFile(String path) {
+        this(new File(path));
+    }
+
+    public TestFile(URL url) {
+        this(toUri(url));
+    }
+
+    public TestFile usingNativeTools() {
+        useNativeTools = true;
+        return this;
+    }
+
+    Object writeReplace() throws ObjectStreamException {
+        return new File(getAbsolutePath());
+    }
+
+    private static URI toUri(URL url) {
+        try {
+            return url.toURI();
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static File join(File file, Object[] path) {
+        File current = file.getAbsoluteFile();
+        for (Object p : path) {
+            current = new File(current, p.toString());
+        }
+        try {
+            return current.getCanonicalFile();
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("Could not canonicalise '%s'.", current), e);
+        }
+    }
+
+    public TestFile file(Object... path) {
+        try {
+            return new TestFile(this, path);
+        } catch (RuntimeException e) {
+            throw new RuntimeException(String.format("Could not locate file '%s' relative to '%s'.", Arrays.toString(path), this), e);
+        }
+    }
+
+    public List<TestFile> files(Object... paths) {
+        List<TestFile> files = new ArrayList<TestFile>();
+        for (Object path : paths) {
+            files.add(file(path));
+        }
+        return files;
+    }
+
+    public TestFile writelns(String... lines) {
+        return writelns(Arrays.asList(lines));
+    }
+
+    public TestFile write(Object content) {
+        try {
+            FileUtils.writeStringToFile(this, content.toString());
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("Could not write to test file '%s'", this), e);
+        }
+        return this;
+    }
+
+    public TestFile leftShift(Object content) {
+        getParentFile().mkdirs();
+        try {
+            DefaultGroovyMethods.leftShift(this, content);
+            return this;
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("Could not append to test file '%s'", this), e);
+        }
+    }
+
+    public TestFile[] listFiles() {
+        File[] children = super.listFiles();
+        TestFile[] files = new TestFile[children.length];
+        for (int i = 0; i < children.length; i++) {
+            File child = children[i];
+            files[i] = new TestFile(child);
+        }
+        return files;
+    }
+
+    public String getText() {
+        assertIsFile();
+        try {
+            return FileUtils.readFileToString(this);
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("Could not read from test file '%s'", this), e);
+        }
+    }
+
+    public Map<String, String> getProperties() {
+        assertIsFile();
+        Properties properties = new Properties();
+        try {
+            FileInputStream inStream = new FileInputStream(this);
+            try {
+                properties.load(inStream);
+            } finally {
+                inStream.close();
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        Map<String, String> map = new HashMap<String, String>();
+        for (Object key : properties.keySet()) {
+            map.put(key.toString(), properties.getProperty(key.toString()));
+        }
+        return map;
+    }
+
+    public Manifest getManifest() {
+        assertIsFile();
+        try {
+            JarFile jarFile = new JarFile(this);
+            try {
+                return jarFile.getManifest();
+            } finally {
+                jarFile.close();
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public List<String> linesThat(Matcher<? super String> matcher) {
+        try {
+            BufferedReader reader = new BufferedReader(new FileReader(this));
+            try {
+                List<String> lines = new ArrayList<String>();
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    if (matcher.matches(line)) {
+                        lines.add(line);
+                    }
+                }
+                return lines;
+            } finally {
+                reader.close();
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void unzipTo(File target) {
+        assertIsFile();
+        new TestFileHelper(this).unzipTo(target, useNativeTools);
+    }
+
+    public void untarTo(File target) {
+        assertIsFile();
+
+        new TestFileHelper(this).untarTo(target, useNativeTools);
+    }
+
+    public void copyTo(File target) {
+        if (isDirectory()) {
+            try {
+                FileUtils.copyDirectory(this, target);
+            } catch (IOException e) {
+                throw new RuntimeException(String.format("Could not copy test directory '%s' to '%s'", this,
+                        target), e);
+            }
+        } else {
+            try {
+                FileUtils.copyFile(this, target);
+            } catch (IOException e) {
+                throw new RuntimeException(String.format("Could not copy test file '%s' to '%s'", this, target), e);
+            }
+        }
+    }
+
+    public void copyFrom(File target) {
+        new TestFile(target).copyTo(this);
+    }
+
+    public void copyFrom(URL resource) {
+        try {
+            FileUtils.copyURLToFile(resource, this);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    public void moveToDirectory(File target) {
+        if (target.exists() && !target.isDirectory()) {
+                throw new RuntimeException(String.format("Target '%s' is not a directory", target));
+        }
+        try {
+            FileUtils.moveFileToDirectory(this, target, true);
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("Could not move test file '%s' to directory '%s'", this, target), e);
+        }
+    }
+
+    public TestFile touch() {
+        try {
+            FileUtils.touch(this);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        assertIsFile();
+        return this;
+    }
+
+    /**
+     * Creates a directory structure specified by the given closure.
+     * <pre>
+     * dir.create {
+     *     subdir1 {
+     *        file 'somefile.txt'
+     *     }
+     *     subdir2 { nested { file 'someFile' } }
+     * }
+     * </pre>
+     */
+    public TestFile create(Closure structure) {
+        assertTrue(isDirectory() || mkdirs());
+        new TestWorkspaceBuilder(this).apply(structure);
+        return this;
+    }
+
+    @Override
+    public TestFile getParentFile() {
+        return super.getParentFile() == null ? null : new TestFile(super.getParentFile());
+    }
+
+    @Override
+    public String toString() {
+        return getPath();
+    }
+
+    public TestFile writelns(Iterable<String> lines) {
+        Formatter formatter = new Formatter();
+        for (String line : lines) {
+            formatter.format("%s%n", line);
+        }
+        return write(formatter);
+    }
+
+    public TestFile assertExists() {
+        assertTrue(String.format("%s does not exist", this), exists());
+        return this;
+    }
+
+    public TestFile assertIsFile() {
+        assertTrue(String.format("%s is not a file", this), isFile());
+        return this;
+    }
+
+    public TestFile assertIsDir() {
+        assertTrue(String.format("%s is not a directory", this), isDirectory());
+        return this;
+    }
+
+    public TestFile assertDoesNotExist() {
+        assertFalse(String.format("%s should not exist", this), exists());
+        return this;
+    }
+
+    public TestFile assertContents(Matcher<String> matcher) {
+        assertThat(getText(), matcher);
+        return this;
+    }
+
+    public TestFile assertIsCopyOf(TestFile other) {
+        assertIsFile();
+        other.assertIsFile();
+        assertEquals(String.format("%s is not the same length as %s", this, other), other.length(), this.length());
+        assertTrue(String.format("%s does not have the same content as %s", this, other), Arrays.equals(getHash("MD5"), other.getHash("MD5")));
+        return this;
+    }
+
+    private byte[] getHash(String algorithm) {
+        try {
+            MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
+            messageDigest.update(FileUtils.readFileToByteArray(this));
+            return messageDigest.digest();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public TestFile assertPermissions(Matcher<String> matcher) {
+        assertThat(String.format("mismatched permissions for '%s'", this), getPermissions(), matcher);
+        return this;
+    }
+
+    public String readLink() {
+        assertExists();
+        return new TestFileHelper(this).readLink();
+    }
+
+    public String getPermissions() {
+        assertExists();
+        return new TestFileHelper(this).getPermissions();
+    }
+
+    public TestFile setPermissions(String permissions) {
+        assertExists();
+        new TestFileHelper(this).setPermissions(permissions);
+        return this;
+    }
+
+    public TestFile setMode(int mode) {
+        assertExists();
+        new TestFileHelper(this).setMode(mode);
+        return this;
+    }
+
+    public int getMode() {
+        assertExists();
+        return new TestFileHelper(this).getMode();
+    }
+
+    /**
+     * Asserts that this file contains exactly the given set of descendants.
+     */
+    public TestFile assertHasDescendants(String... descendants) {
+        Set<String> actual = new TreeSet<String>();
+        assertIsDir();
+        visit(actual, "", this);
+        Set<String> expected = new TreeSet<String>(Arrays.asList(descendants));
+
+        Set<String> extras = new TreeSet<String>(actual);
+        extras.removeAll(expected);
+        Set<String> missing = new TreeSet<String>(expected);
+        missing.removeAll(actual);
+
+        assertEquals(String.format("For dir: %s, extra files: %s, missing files: %s, expected: %s", this, extras, missing, expected), expected, actual);
+
+        return this;
+    }
+
+    public TestFile assertIsEmptyDir() {
+        if (exists()) {
+            assertIsDir();
+            assertHasDescendants();
+        }
+        return this;
+    }
+
+    private void visit(Set<String> names, String prefix, File file) {
+        for (File child : file.listFiles()) {
+            if (child.isFile()) {
+                names.add(prefix + child.getName());
+            } else if (child.isDirectory()) {
+                visit(names, prefix + child.getName() + "/", child);
+            }
+        }
+    }
+
+    public boolean isSelfOrDescendent(File file) {
+        if (file.getAbsolutePath().equals(getAbsolutePath())) {
+            return true;
+        }
+        return file.getAbsolutePath().startsWith(getAbsolutePath() + File.separatorChar);
+    }
+
+    public TestFile createDir() {
+        if (mkdirs()) {
+            return this;
+        }
+        if (isDirectory()) {
+            return this;
+        }
+        throw new AssertionError("Problems creating dir: " + this
+                + ". Diagnostics: exists=" + this.exists() + ", isFile=" + this.isFile() + ", isDirectory=" + this.isDirectory());
+    }
+
+    public TestFile createDir(Object path) {
+        return new TestFile(this, path).createDir();
+    }
+
+    public TestFile deleteDir() {
+        new TestFileHelper(this).delete(useNativeTools);
+        return this;
+    }
+
+    /**
+     * Attempts to delete this directory, ignoring failures to do so.
+     * @return this
+     */
+    public TestFile maybeDeleteDir() {
+        try {
+            deleteDir();
+        } catch (RuntimeException e) {
+            // Ignore
+        }
+        return this;
+    }
+
+    public TestFile createFile() {
+        new TestFile(getParentFile()).createDir();
+        try {
+            assertTrue(isFile() || createNewFile());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return this;
+    }
+
+    public TestFile createFile(Object path) {
+        return file(path).createFile();
+    }
+
+    public TestFile createZip(Object path) {
+        Zip zip = new Zip();
+        zip.setWhenempty((Zip.WhenEmpty) Zip.WhenEmpty.getInstance(Zip.WhenEmpty.class, "create"));
+        TestFile zipFile = file(path);
+        zip.setDestFile(zipFile);
+        zip.setBasedir(this);
+        zip.setExcludes("**");
+        execute(zip);
+        return zipFile;
+    }
+
+    public TestFile zipTo(TestFile zipFile){
+        new TestFileHelper(this).zipTo(zipFile, useNativeTools);
+        return this;
+    }
+
+    public TestFile tarTo(TestFile tarFile) {
+        new TestFileHelper(this).tarTo(tarFile, useNativeTools);
+        return this;
+    }
+
+    public TestFile tgzTo(TestFile tarFile) {
+        Tar tar = new Tar();
+        tar.setBasedir(this);
+        tar.setDestFile(tarFile);
+        tar.setCompression((Tar.TarCompressionMethod) EnumeratedAttribute.getInstance(Tar.TarCompressionMethod.class, "gzip"));
+        execute(tar);
+        return this;
+    }
+
+    public TestFile tbzTo(TestFile tarFile) {
+        Tar tar = new Tar();
+        tar.setBasedir(this);
+        tar.setDestFile(tarFile);
+        tar.setCompression((Tar.TarCompressionMethod) EnumeratedAttribute.getInstance(Tar.TarCompressionMethod.class, "bzip2"));
+        execute(tar);
+        return this;
+    }
+
+    private void execute(Task task) {
+        task.setProject(new Project());
+        task.execute();
+    }
+
+    public Snapshot snapshot() {
+        assertIsFile();
+        return new Snapshot(lastModified(), getHash("MD5"));
+    }
+
+    public void assertHasChangedSince(Snapshot snapshot) {
+        Snapshot now = snapshot();
+        assertTrue(now.modTime != snapshot.modTime || !Arrays.equals(now.hash, snapshot.hash));
+    }
+
+    public void assertContentsHaveNotChangedSince(Snapshot snapshot) {
+        Snapshot now = snapshot();
+        assertArrayEquals(String.format("contents of %s has changed", this), snapshot.hash, now.hash);
+    }
+
+    public void assertHasNotChangedSince(Snapshot snapshot) {
+        Snapshot now = snapshot();
+        assertEquals(String.format("last modified time of %s has changed", this), snapshot.modTime, now.modTime);
+        assertArrayEquals(String.format("contents of %s has changed", this), snapshot.hash, now.hash);
+    }
+
+    public void writeProperties(Map<?, ?> properties) {
+        Properties props = new Properties();
+        props.putAll(properties);
+        try {
+            FileOutputStream stream = new FileOutputStream(this);
+            try {
+                props.store(stream, "comment");
+            } finally {
+                stream.close();
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    public Map<String, ?> exec(Object... args) {
+        return new TestFileHelper(this).exec(args);
+    }
+
+    public class Snapshot {
+        private final long modTime;
+        private final byte[] hash;
+
+        public Snapshot(long modTime, byte[] hash) {
+            this.modTime = modTime;
+            this.hash = hash;
+        }
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestFileHelper.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestFileHelper.groovy
new file mode 100755
index 0000000..1f09902
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestFileHelper.groovy
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.test.fixtures.file
+
+import org.apache.commons.io.FileUtils
+import org.apache.commons.lang.StringUtils
+import org.apache.tools.ant.Project
+import org.apache.tools.ant.taskdefs.Expand
+import org.apache.tools.ant.taskdefs.Tar
+import org.apache.tools.ant.taskdefs.Untar
+import org.apache.tools.ant.taskdefs.Zip
+
+import java.util.zip.ZipInputStream
+
+import static org.hamcrest.Matchers.equalTo
+import static org.junit.Assert.assertThat
+import static org.junit.Assert.assertTrue
+
+class TestFileHelper {
+    TestFile file
+
+    TestFileHelper(TestFile file) {
+        this.file = file
+    }
+
+    void unzipTo(File target, boolean nativeTools) {
+        // Check that each directory in hierarchy is present
+        file.withInputStream {InputStream instr ->
+            def dirs = [] as Set
+            def zipStr = new ZipInputStream(instr)
+            def entry
+            while (entry = zipStr.getNextEntry()) {
+                if (entry.directory) {
+                    assertTrue("Duplicate directory '$entry.name'", dirs.add(entry.name))
+                }
+                if (!entry.name.contains('/')) {
+                    continue
+                }
+                def parent = StringUtils.substringBeforeLast(entry.name, '/') + '/'
+                assertTrue("Missing dir '$parent'", dirs.contains(parent))
+            }
+        }
+
+        if (nativeTools && isUnix()) {
+            def process = ['unzip', '-o', file.absolutePath, '-d', target.absolutePath].execute()
+            process.consumeProcessOutput(System.out, System.err)
+            assertThat(process.waitFor(), equalTo(0))
+            return
+        }
+
+        def unzip = new Expand()
+        unzip.src = file
+        unzip.dest = target
+
+        unzip.project = new Project()
+        unzip.execute()
+    }
+
+    void untarTo(File target, boolean nativeTools) {
+        if (nativeTools && isUnix()) {
+            target.mkdirs()
+            def builder = new ProcessBuilder(['tar', '-xpf', file.absolutePath])
+            builder.directory(target)
+            def process = builder.start()
+            process.consumeProcessOutput()
+            assertThat(process.waitFor(), equalTo(0))
+            return
+        }
+
+        def untar = new Untar()
+        untar.setSrc(file)
+        untar.setDest(target)
+
+        if (file.name.endsWith(".tgz")) {
+            def method = new Untar.UntarCompressionMethod()
+            method.value = "gzip"
+            untar.compression = method
+        } else if (file.name.endsWith(".tbz2")) {
+            def method = new Untar.UntarCompressionMethod()
+            method.value = "bzip2"
+            untar.compression = method
+        }
+
+        untar.project = new Project()
+        untar.execute()
+    }
+
+    private boolean isUnix() {
+        return !System.getProperty('os.name').toLowerCase().contains('windows')
+    }
+
+    String getPermissions() {
+        if (!isUnix()) {
+            return "-rwxr-xr-x"
+        }
+
+        def process = ["ls", "-ld", file.absolutePath].execute()
+        def result = process.inputStream.text
+        def error = process.errorStream.text
+        def retval = process.waitFor()
+        if (retval != 0) {
+            throw new RuntimeException("Could not list permissions for '$file': $error")
+        }
+        def perms = result.split()[0]
+        assert perms.matches("[d\\-][rwx\\-]{9}[@\\+]?")
+        return perms.substring(1, 10)
+    }
+
+    void setPermissions(String permissions) {
+        if (!isUnix()) {
+            return
+        }
+        int m = toMode(permissions)
+        setMode(m)
+    }
+
+    void setMode(int mode) {
+        def process = ["chmod", Integer.toOctalString(mode), file.absolutePath].execute()
+        def error = process.errorStream.text
+        def retval = process.waitFor()
+        if (retval != 0) {
+            throw new RuntimeException("Could not set permissions for '$file': $error")
+        }
+    }
+
+    private int toMode(String permissions) {
+        int m = [6, 3, 0].inject(0) { mode, pos ->
+            mode |= permissions[9 - pos - 3] == 'r' ? 4 << pos : 0
+            mode |= permissions[9 - pos - 2] == 'w' ? 2 << pos : 0
+            mode |= permissions[9 - pos - 1] == 'x' ? 1 << pos : 0
+            return mode
+        }
+        return m
+    }
+
+    int getMode() {
+        return toMode(getPermissions())
+    }
+
+    void delete(boolean nativeTools) {
+        if (isUnix() && nativeTools) {
+            def process = ["rm", "-rf", file.absolutePath].execute()
+            def error = process.errorStream.text
+            def retval = process.waitFor()
+            if (retval != 0) {
+                throw new RuntimeException("Could not delete '$file': $error")
+            }
+        } else {
+            FileUtils.deleteQuietly(file);
+        }
+    }
+
+    String readLink() {
+        def process = ["readlink", file.absolutePath].execute()
+        def error = process.errorStream.text
+        def retval = process.waitFor()
+        if (retval != 0) {
+            throw new RuntimeException("Could not read link '$file': $error")
+        }
+        return process.inputStream.text.trim()
+    }
+
+    Map<String, ?> exec(Object... args) {
+        def process = ([file.absolutePath] + (args as List)).execute()
+        def output = process.inputStream.text
+        def error = process.errorStream.text
+        if (process.waitFor() != 0) {
+            throw new RuntimeException("Could not execute $file. Error: $error, Output: $output")
+        }
+        return [out: output, error: error]
+    }
+
+    public void zipTo(TestFile zipFile, boolean nativeTools) {
+        if (nativeTools && isUnix()) {
+            def process = ['zip', zipFile.absolutePath, "-r", file.name].execute(null, zipFile.parentFile)
+            process.consumeProcessOutput(System.out, System.err)
+            assertThat(process.waitFor(), equalTo(0))
+        } else {
+            Zip zip = new Zip();
+            zip.setBasedir(file);
+            zip.setDestFile(zipFile);
+            zip.setProject(new Project());
+            def whenEmpty = new Zip.WhenEmpty()
+            whenEmpty.setValue("create")
+            zip.setWhenempty(whenEmpty);
+            zip.execute();
+        }
+    }
+
+    public void tarTo(TestFile tarFile, boolean nativeTools) {
+        if (nativeTools && isUnix()) {
+            def process = ['tar', "-cf", tarFile.absolutePath, file.name].execute(null, tarFile.parentFile)
+            process.consumeProcessOutput(System.out, System.err)
+            assertThat(process.waitFor(), equalTo(0))
+        } else {
+            Tar tar = new Tar();
+            tar.setBasedir(file);
+            tar.setDestFile(tarFile);
+            tar.setProject(new Project())
+            tar.execute()
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestNameTestDirectoryProvider.java b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestNameTestDirectoryProvider.java
new file mode 100644
index 0000000..0209421
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestNameTestDirectoryProvider.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.test.fixtures.file;
+
+import org.apache.commons.lang.StringUtils;
+import org.junit.rules.MethodRule;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A JUnit rule which provides a unique temporary folder for the test.
+ */
+public class TestNameTestDirectoryProvider implements MethodRule, TestRule, TestDirectoryProvider {
+    private TestFile dir;
+    private String prefix;
+    private static TestFile root;
+    private static AtomicInteger testCounter = new AtomicInteger(1);
+
+    static {
+        // NOTE: the space in the directory name is intentional
+        root = new TestFile(new File("build/tmp/test files"));
+    }
+
+    private String determinePrefix() {
+        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
+        for (StackTraceElement element : stackTrace) {
+            if (element.getClassName().endsWith("Test") || element.getClassName().endsWith("Spec")) {
+                return StringUtils.substringAfterLast(element.getClassName(), ".") + "/unknown-test-" + testCounter.getAndIncrement();
+            }
+        }
+        return "unknown-test-class-" + testCounter.getAndIncrement();
+    }
+
+    public Statement apply(final Statement base, final FrameworkMethod method, final Object target) {
+        init(method.getName(), target.getClass().getSimpleName());
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                base.evaluate();
+                getTestDirectory().maybeDeleteDir();
+                // Don't delete on failure
+            }
+        };
+    }
+
+    public Statement apply(final Statement base, Description description) {
+        init(description.getMethodName(), description.getTestClass().getSimpleName());
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                base.evaluate();
+                getTestDirectory().maybeDeleteDir();
+                // Don't delete on failure
+            }
+        };
+    }
+
+    private void init(String methodName, String className) {
+        if (methodName == null) {
+            // must be a @ClassRule; use the rule's class name instead
+            methodName = getClass().getSimpleName();
+        }
+        if (prefix == null) {
+            String safeMethodName = methodName.replaceAll("\\s", "_").replace(File.pathSeparator, "_").replace(":", "_");
+            if (safeMethodName.length() > 64) {
+                safeMethodName = safeMethodName.substring(0, 32) + "..." + safeMethodName.substring(safeMethodName.length() - 32);
+            }
+            prefix = String.format("%s/%s", className, safeMethodName);
+        }
+    }
+
+    public static TestNameTestDirectoryProvider newInstance() {
+        return new TestNameTestDirectoryProvider();
+    }
+
+    public static TestNameTestDirectoryProvider newInstance(FrameworkMethod method, Object target) {
+        TestNameTestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider();
+        testDirectoryProvider.init(method.getName(), target.getClass().getSimpleName());
+        return testDirectoryProvider;
+    }
+
+    public TestFile getTestDirectory() {
+        if (dir == null) {
+            if (prefix == null) {
+                // This can happen if this is used in a constructor or a @Before method. It also happens when using
+                // @RunWith(SomeRunner) when the runner does not support rules.
+                prefix = determinePrefix();
+            }
+            for (int counter = 1; true; counter++) {
+                dir = root.file(counter == 1 ? prefix : String.format("%s%d", prefix, counter));
+                if (dir.mkdirs()) {
+                    break;
+                }
+            }
+        }
+        return dir;
+    }
+
+    public TestFile file(Object... path) {
+        return getTestDirectory().file((Object[]) path);
+    }
+
+    public TestFile createFile(Object... path) {
+        return file((Object[]) path).createFile();
+    }
+
+    public TestFile createDir(Object... path) {
+        return file((Object[]) path).createDir();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestWorkspaceBuilder.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestWorkspaceBuilder.groovy
new file mode 100644
index 0000000..d97b03f
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestWorkspaceBuilder.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.test.fixtures.file
+
+/**
+ * Used in TestFile.create().
+ *
+ * Should be inner class of TestFile, but can't because Groovy has issues with inner classes as delegates.
+ */
+class TestWorkspaceBuilder {
+    def TestFile baseDir
+
+    def TestWorkspaceBuilder(TestFile baseDir) {
+        this.baseDir = baseDir
+    }
+
+    def apply(Closure cl) {
+        cl.delegate = this
+        cl.resolveStrategy = Closure.DELEGATE_FIRST
+        cl()
+    }
+
+    def file(String name) {
+        TestFile file = baseDir.file(name)
+        file.write('some content')
+        file
+    }
+
+    def setMode(int mode) {
+        baseDir.mode = mode
+    }
+
+    def methodMissing(String name, Object args) {
+        if (args.length == 1 && args[0] instanceof Closure) {
+            baseDir.file(name).create(args[0])
+        }
+        else {
+            throw new MissingMethodException(name, getClass(), args)
+        }
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/ExceptionAssert.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/ExceptionAssert.groovy
deleted file mode 100644
index aeb26b5..0000000
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/ExceptionAssert.groovy
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.testing.internal.util
-
-/**
- * Helps testing if the exception messages chain (including causes)
- * contain given information.
- * <p>
- * by Szczepan Faber, created at: 3/6/12
- */
-public class ExceptionAssert {
-
-    private final Throwable target
-
-    public ExceptionAssert(Throwable target) {
-        this.target = target
-    }
-
-    public static assertThat(Throwable t) {
-        return new ExceptionAssert(t);
-    }
-
-    /**
-     * Asserts if given exception message/ exception classname contains given information (substring)
-     * Recursively checks the cause chain.
-     *
-     * @param information wanted substring
-     */
-    void containsInfo(String information) {
-        def ex = target
-        def checked = []
-        while(ex != null) {
-            checked << ex.toString()
-            if (ex.toString().contains(information)) {
-                return
-            }
-            
-            ex = ex.cause
-        }
-        throw new AssertionError((Object) "Unable to find '$information' in the exception messages chain."
-            + "\nTested following messages: \n------\n > ${checked.join('\n > ')}\n------")
-    }
-
-    String toString() {
-        return "target=$target"
-    }
-}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/GradlewRunner.java b/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/GradlewRunner.java
index d93c8e5..c4824cb 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/GradlewRunner.java
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/GradlewRunner.java
@@ -18,6 +18,9 @@ package org.gradle.testing.internal.util;
 
 import java.io.*;
 
+/**
+ * This is used to launch Gradle from within IDEA. See gradle/idea.gradle.
+ */
 public class GradlewRunner {
     public static void main(String[] args) {
         Process process = null;
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/Assertions.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/Assertions.groovy
new file mode 100644
index 0000000..3019184
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/Assertions.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.util;
+
+/**
+ * by Szczepan Faber, created at: 12/18/12
+ */
+class Assertions {
+
+    private final Object source
+
+    static Assertions assertThat(Object source) {
+        new Assertions(source)
+    }
+
+    Assertions(Object source) {
+        this.source = source
+    }
+
+    Assertions doesNotShareStateWith(Object target) {
+        //this is really basic - does not work well with primitives, immutables like String, etc
+        //grow it if needed
+        source.getClass().getDeclaredFields().each {
+            if (!it.name.startsWith('$') && it.name != 'metaClass') { //filter out groovy stuff
+                it.setAccessible(true)
+                assert !it.get(source).is(it.get(target)) : "field value '$it.name' should not be shared between the instances."
+            }
+        }
+        this
+    }
+}
\ No newline at end of file
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/Resources.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/Resources.java
index df582ca..774560e 100755
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/Resources.java
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/Resources.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.util;
 
+import org.gradle.test.fixtures.file.TestFile;
 import org.junit.rules.MethodRule;
 import org.junit.runners.model.FrameworkMethod;
 import org.junit.runners.model.Statement;
@@ -23,7 +24,8 @@ import java.io.File;
 import java.net.URISyntaxException;
 import java.net.URL;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 
 /**
  * A JUnit rule which helps locate test resources.
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/RuleHelper.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/RuleHelper.java
new file mode 100644
index 0000000..607e40d
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/RuleHelper.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+
+public class RuleHelper {
+    public static <T> T getField(Object target, Class<T> type) {
+        T value = findField(target, type);
+        if (value != null) {
+            return value;
+        }
+        throw new RuntimeException(String.format("Cannot find a field of type %s for test class %s.",
+                type.getSimpleName(), target.getClass().getSimpleName()));
+    }
+
+    public static <T> T findField(Object target, Class<T> type) {
+        List<T> matches = new ArrayList<T>();
+        for (Class<?> cl = target.getClass(); cl != Object.class; cl = cl.getSuperclass()) {
+            for (Field field : cl.getDeclaredFields()) {
+                if (!Modifier.isStatic(field.getModifiers()) && type.isAssignableFrom(field.getType())) {
+                    field.setAccessible(true);
+                    try {
+                        matches.add(type.cast(field.get(target)));
+                    } catch (IllegalAccessException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        }
+        if (matches.isEmpty()) {
+            return null;
+        }
+        if (matches.size() > 1) {
+            throw new RuntimeException(String.format("Multiple %s fields found for test class %s.",
+                    type.getSimpleName(), target.getClass().getSimpleName()));
+        }
+        return matches.get(0);
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TemporaryFolder.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TemporaryFolder.java
deleted file mode 100644
index 238603d..0000000
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TemporaryFolder.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.util;
-
-import org.apache.commons.lang.StringUtils;
-import org.junit.rules.MethodRule;
-import org.junit.runners.model.FrameworkMethod;
-import org.junit.runners.model.Statement;
-
-import java.io.File;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * A JUnit rule which provides a unique temporary folder for the test.
- */
-public class TemporaryFolder implements MethodRule, TestFileContext {
-    private TestFile dir;
-    private String prefix;
-    private static TestFile root;
-    private static AtomicInteger testCounter = new AtomicInteger(1);
-
-    static {
-        // NOTE: the space in the directory name is intentional
-        root = new TestFile(new File("build/tmp/test files"));
-    }
-
-    public TestFile getDir() {
-        if (dir == null) {
-            if (prefix == null) {
-                // This can happen if this is used in a constructor or a @Before method. It also happens when using
-                // @RunWith(SomeRunner) when the runner does not support rules.
-                prefix = determinePrefix();
-            }
-            for (int counter = 1; true; counter++) {
-                dir = root.file(counter == 1 ? prefix : String.format("%s%d", prefix, counter));
-                if (dir.mkdirs()) {
-                    break;
-                }
-            }
-        }
-        return dir;
-    }
-
-    private String determinePrefix() {
-        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
-        for (StackTraceElement element : stackTrace) {
-            if (element.getClassName().endsWith("Test") || element.getClassName().endsWith("Spec")) {
-                return StringUtils.substringAfterLast(element.getClassName(), ".") + "/unknown-test-" + testCounter.getAndIncrement();
-            }
-        }
-        return "unknown-test-class-" + testCounter.getAndIncrement();
-    }
-
-    public Statement apply(final Statement base, final FrameworkMethod method, final Object target) {
-        init(method, target);
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                base.evaluate();
-                getDir().maybeDeleteDir();
-                // Don't delete on failure
-            }
-        };
-    }
-
-    private void init(FrameworkMethod method, Object target) {
-        if (prefix == null) {
-            String safeMethodName = method.getName().replaceAll("\\s", "_").replace(File.pathSeparator, "_").replace(":", "_");
-            if (safeMethodName.length() > 64) {
-                safeMethodName = safeMethodName.substring(0, 32) + "..." + safeMethodName.substring(safeMethodName.length() - 32);
-            }
-            prefix = String.format("%s/%s", target.getClass().getSimpleName(), safeMethodName);
-        }
-    }
-
-    public static TemporaryFolder newInstance() {
-        return new TemporaryFolder();
-    }
-
-    public static TemporaryFolder newInstance(FrameworkMethod method, Object target) {
-        TemporaryFolder temporaryFolder = new TemporaryFolder();
-        temporaryFolder.init(method, target);
-        return temporaryFolder;
-    }
-
-    public TestFile getTestDir() {
-        return getDir();
-    }
-
-    public TestFile file(Object... path) {
-        return getDir().file((Object[]) path);
-    }
-
-    public TestFile createFile(Object... path) {
-        return file((Object[]) path).createFile();
-    }
-
-    public TestFile createDir(Object... path) {
-        return file((Object[]) path).createDir();
-    }
-}
\ No newline at end of file
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestDirHelper.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestDirHelper.groovy
deleted file mode 100644
index 4d614b5..0000000
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestDirHelper.groovy
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.util
-
-class TestDirHelper {
-    def TestFile baseDir
-
-    def TestDirHelper(TestFile baseDir) {
-        this.baseDir = baseDir
-    }
-
-    def apply(Closure cl) {
-        cl.delegate = this
-        cl.resolveStrategy = Closure.DELEGATE_FIRST
-        cl()
-    }
-
-    def file(String name) {
-        TestFile file = baseDir.file(name)
-        file.write('some content')
-        file
-    }
-
-    def setMode(int mode) {
-        baseDir.mode = mode
-    }
-
-    def methodMissing(String name, Object args) {
-        if (args.length == 1 && args[0] instanceof Closure) {
-            baseDir.file(name).create(args[0])
-        }
-        else {
-            throw new MissingMethodException(name, getClass(), args)
-        }
-    }
-}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFile.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFile.java
deleted file mode 100644
index 064acb8..0000000
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFile.java
+++ /dev/null
@@ -1,559 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.util;
-
-import groovy.lang.Closure;
-import org.apache.commons.io.FileUtils;
-import org.apache.tools.ant.Project;
-import org.apache.tools.ant.Task;
-import org.apache.tools.ant.taskdefs.Tar;
-import org.apache.tools.ant.taskdefs.Zip;
-import org.apache.tools.ant.types.EnumeratedAttribute;
-import org.codehaus.groovy.runtime.DefaultGroovyMethods;
-import org.hamcrest.Matcher;
-
-import java.io.*;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.security.MessageDigest;
-import java.util.*;
-import java.util.jar.JarFile;
-import java.util.jar.Manifest;
-
-import static org.junit.Assert.*;
-
-public class TestFile extends File implements TestFileContext {
-    private boolean useNativeTools;
-
-    public TestFile(File file, Object... path) {
-        super(join(file, path).getAbsolutePath());
-    }
-
-    public TestFile(URI uri) {
-        this(new File(uri));
-    }
-
-    public TestFile(String path) {
-        this(new File(path));
-    }
-
-    public TestFile(URL url) {
-        this(toUri(url));
-    }
-
-    public TestFile getTestDir() {
-        return this;
-    }
-
-    public TestFile usingNativeTools() {
-        useNativeTools = true;
-        return this;
-    }
-
-    Object writeReplace() throws ObjectStreamException {
-        return new File(getAbsolutePath());
-    }
-
-    private static URI toUri(URL url) {
-        try {
-            return url.toURI();
-        } catch (URISyntaxException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private static File join(File file, Object[] path) {
-        File current = file.getAbsoluteFile();
-        for (Object p : path) {
-            current = new File(current, p.toString());
-        }
-        try {
-            return current.getCanonicalFile();
-        } catch (IOException e) {
-            throw new RuntimeException(String.format("Could not canonicalise '%s'.", current), e);
-        }
-    }
-
-    public TestFile file(Object... path) {
-        try {
-            return new TestFile(this, path);
-        } catch (RuntimeException e) {
-            throw new RuntimeException(String.format("Could not locate file '%s' relative to '%s'.", Arrays.toString(path), this), e);
-        }
-    }
-
-    public List<TestFile> files(Object... paths) {
-        List<TestFile> files = new ArrayList<TestFile>();
-        for (Object path : paths) {
-            files.add(file(path));
-        }
-        return files;
-    }
-
-    public TestFile writelns(String... lines) {
-        return writelns(Arrays.asList(lines));
-    }
-
-    public TestFile write(Object content) {
-        try {
-            FileUtils.writeStringToFile(this, content.toString());
-        } catch (IOException e) {
-            throw new RuntimeException(String.format("Could not write to test file '%s'", this), e);
-        }
-        return this;
-    }
-
-    public TestFile leftShift(Object content) {
-        getParentFile().mkdirs();
-        try {
-            DefaultGroovyMethods.leftShift(this, content);
-            return this;
-        } catch (IOException e) {
-            throw new RuntimeException(String.format("Could not append to test file '%s'", this), e);
-        }
-    }
-
-    public TestFile[] listFiles() {
-        File[] children = super.listFiles();
-        TestFile[] files = new TestFile[children.length];
-        for (int i = 0; i < children.length; i++) {
-            File child = children[i];
-            files[i] = new TestFile(child);
-        }
-        return files;
-    }
-
-    public String getText() {
-        assertIsFile();
-        try {
-            return FileUtils.readFileToString(this);
-        } catch (IOException e) {
-            throw new RuntimeException(String.format("Could not read from test file '%s'", this), e);
-        }
-    }
-
-    public Map<String, String> getProperties() {
-        assertIsFile();
-        Properties properties = new Properties();
-        try {
-            FileInputStream inStream = new FileInputStream(this);
-            try {
-                properties.load(inStream);
-            } finally {
-                inStream.close();
-            }
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-        Map<String, String> map = new HashMap<String, String>();
-        for (Object key : properties.keySet()) {
-            map.put(key.toString(), properties.getProperty(key.toString()));
-        }
-        return map;
-    }
-
-    public Manifest getManifest() {
-        assertIsFile();
-        try {
-            JarFile jarFile = new JarFile(this);
-            try {
-                return jarFile.getManifest();
-            } finally {
-                jarFile.close();
-            }
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    public List<String> linesThat(Matcher<? super String> matcher) {
-        try {
-            BufferedReader reader = new BufferedReader(new FileReader(this));
-            try {
-                List<String> lines = new ArrayList<String>();
-                String line;
-                while ((line = reader.readLine()) != null) {
-                    if (matcher.matches(line)) {
-                        lines.add(line);
-                    }
-                }
-                return lines;
-            } finally {
-                reader.close();
-            }
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    public void unzipTo(File target) {
-        assertIsFile();
-        new TestFileHelper(this).unzipTo(target, useNativeTools);
-    }
-
-    public void untarTo(File target) {
-        assertIsFile();
-
-        new TestFileHelper(this).untarTo(target, useNativeTools);
-    }
-
-    public void copyTo(File target) {
-        if (isDirectory()) {
-            try {
-                FileUtils.copyDirectory(this, target);
-            } catch (IOException e) {
-                throw new RuntimeException(String.format("Could not copy test directory '%s' to '%s'", this,
-                        target), e);
-            }
-        } else {
-            try {
-                FileUtils.copyFile(this, target);
-            } catch (IOException e) {
-                throw new RuntimeException(String.format("Could not copy test file '%s' to '%s'", this, target), e);
-            }
-        }
-    }
-
-    public void copyFrom(File target) {
-        new TestFile(target).copyTo(this);
-    }
-
-    public void copyFrom(URL resource) {
-        try {
-            FileUtils.copyURLToFile(resource, this);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-    
-    public void moveToDirectory(File target) {
-        if (target.exists() && !target.isDirectory()) {
-                throw new RuntimeException(String.format("Target '%s' is not a directory", target));
-        }
-        try {
-            FileUtils.moveFileToDirectory(this, target, true);
-        } catch (IOException e) {
-            throw new RuntimeException(String.format("Could not move test file '%s' to directory '%s'", this, target), e);
-        }
-    }
-
-    public TestFile touch() {
-        try {
-            FileUtils.touch(this);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-        assertIsFile();
-        return this;
-    }
-
-    /**
-     * Creates a directory structure specified by the given closure.
-     * <pre>
-     * dir.create {
-     *     subdir1 {
-     *        file 'somefile.txt'
-     *     }
-     *     subdir2 { nested { file 'someFile' } }
-     * }
-     * </pre>
-     */
-    public TestFile create(Closure structure) {
-        assertTrue(isDirectory() || mkdirs());
-        new TestDirHelper(this).apply(structure);
-        return this;
-    }
-
-    @Override
-    public TestFile getParentFile() {
-        return super.getParentFile() == null ? null : new TestFile(super.getParentFile());
-    }
-
-    @Override
-    public String toString() {
-        return getPath();
-    }
-
-    public TestFile writelns(Iterable<String> lines) {
-        Formatter formatter = new Formatter();
-        for (String line : lines) {
-            formatter.format("%s%n", line);
-        }
-        return write(formatter);
-    }
-
-    public TestFile assertExists() {
-        assertTrue(String.format("%s does not exist", this), exists());
-        return this;
-    }
-
-    public TestFile assertIsFile() {
-        assertTrue(String.format("%s is not a file", this), isFile());
-        return this;
-    }
-
-    public TestFile assertIsDir() {
-        assertTrue(String.format("%s is not a directory", this), isDirectory());
-        return this;
-    }
-
-    public TestFile assertDoesNotExist() {
-        assertFalse(String.format("%s should not exist", this), exists());
-        return this;
-    }
-
-    public TestFile assertContents(Matcher<String> matcher) {
-        assertThat(getText(), matcher);
-        return this;
-    }
-
-    public TestFile assertIsCopyOf(TestFile other) {
-        assertIsFile();
-        other.assertIsFile();
-        assertEquals(String.format("%s is not the same length as %s", this, other), other.length(), this.length());
-        assertTrue(String.format("%s does not have the same content as %s", this, other), Arrays.equals(getHash("MD5"), other.getHash("MD5")));
-        return this;
-    }
-
-    private byte[] getHash(String algorithm) {
-        try {
-            MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
-            messageDigest.update(FileUtils.readFileToByteArray(this));
-            return messageDigest.digest();
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    public TestFile assertPermissions(Matcher<String> matcher) {
-        assertThat(String.format("mismatched permissions for '%s'", this), getPermissions(), matcher);
-        return this;
-    }
-
-    public String readLink() {
-        assertExists();
-        return new TestFileHelper(this).readLink();
-    }
-
-    public String getPermissions() {
-        assertExists();
-        return new TestFileHelper(this).getPermissions();
-    }
-
-    public TestFile setPermissions(String permissions) {
-        assertExists();
-        new TestFileHelper(this).setPermissions(permissions);
-        return this;
-    }
-
-    public TestFile setMode(int mode) {
-        assertExists();
-        new TestFileHelper(this).setMode(mode);
-        return this;
-    }
-
-    public int getMode() {
-        assertExists();
-        return new TestFileHelper(this).getMode();
-    }
-
-    /**
-     * Asserts that this file contains exactly the given set of descendants.
-     */
-    public TestFile assertHasDescendants(String... descendants) {
-        Set<String> actual = new TreeSet<String>();
-        assertIsDir();
-        visit(actual, "", this);
-        Set<String> expected = new TreeSet<String>(Arrays.asList(descendants));
-
-        Set<String> extras = new TreeSet<String>(actual);
-        extras.removeAll(expected);
-        Set<String> missing = new TreeSet<String>(expected);
-        missing.removeAll(actual);
-
-        assertEquals(String.format("For dir: %s, extra files: %s, missing files: %s, expected: %s", this, extras, missing, expected), expected, actual);
-
-        return this;
-    }
-
-    public TestFile assertIsEmptyDir() {
-        if (exists()) {
-            assertIsDir();
-            assertHasDescendants();
-        }
-        return this;
-    }
-
-    private void visit(Set<String> names, String prefix, File file) {
-        for (File child : file.listFiles()) {
-            if (child.isFile()) {
-                names.add(prefix + child.getName());
-            } else if (child.isDirectory()) {
-                visit(names, prefix + child.getName() + "/", child);
-            }
-        }
-    }
-
-    public boolean isSelfOrDescendent(File file) {
-        if (file.getAbsolutePath().equals(getAbsolutePath())) {
-            return true;
-        }
-        return file.getAbsolutePath().startsWith(getAbsolutePath() + File.separatorChar);
-    }
-
-    public TestFile createDir() {
-        if (mkdirs()) {
-            return this;
-        }
-        if (isDirectory()) {
-            return this;
-        }
-        throw new AssertionError("Problems creating dir: " + this
-                + ". Diagnostics: exists=" + this.exists() + ", isFile=" + this.isFile() + ", isDirectory=" + this.isDirectory());
-    }
-
-    public TestFile createDir(Object path) {
-        return new TestFile(this, path).createDir();
-    }
-
-    public TestFile deleteDir() {
-        new TestFileHelper(this).delete(useNativeTools);
-        return this;
-    }
-
-    /**
-     * Attempts to delete this directory, ignoring failures to do so.
-     * @return this
-     */
-    public TestFile maybeDeleteDir() {
-        try {
-            deleteDir();
-        } catch (RuntimeException e) {
-            // Ignore
-        }
-        return this;
-    }
-
-    public TestFile createFile() {
-        new TestFile(getParentFile()).createDir();
-        try {
-            assertTrue(isFile() || createNewFile());
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-        return this;
-    }
-
-    public TestFile createFile(Object path) {
-        return file(path).createFile();
-    }
-
-    public TestFile createZip(Object path) {
-        Zip zip = new Zip();
-        zip.setWhenempty((Zip.WhenEmpty) Zip.WhenEmpty.getInstance(Zip.WhenEmpty.class, "create"));
-        TestFile zipFile = file(path);
-        zip.setDestFile(zipFile);
-        zip.setBasedir(this);
-        zip.setExcludes("**");
-        execute(zip);
-        return zipFile;
-    }
-
-    public TestFile zipTo(TestFile zipFile){
-        new TestFileHelper(this).zipTo(zipFile, useNativeTools);
-        return this;
-    }
-
-    public TestFile tarTo(TestFile tarFile) {
-        new TestFileHelper(this).tarTo(tarFile, useNativeTools);
-        return this;
-    }
-
-    public TestFile tgzTo(TestFile tarFile) {
-        Tar tar = new Tar();
-        tar.setBasedir(this);
-        tar.setDestFile(tarFile);
-        tar.setCompression((Tar.TarCompressionMethod) EnumeratedAttribute.getInstance(Tar.TarCompressionMethod.class, "gzip"));
-        execute(tar);
-        return this;
-    }
-
-    public TestFile tbzTo(TestFile tarFile) {
-        Tar tar = new Tar();
-        tar.setBasedir(this);
-        tar.setDestFile(tarFile);
-        tar.setCompression((Tar.TarCompressionMethod) EnumeratedAttribute.getInstance(Tar.TarCompressionMethod.class, "bzip2"));
-        execute(tar);
-        return this;
-    }
-
-    private void execute(Task task) {
-        task.setProject(new Project());
-        task.execute();
-    }
-
-    public Snapshot snapshot() {
-        assertIsFile();
-        return new Snapshot(lastModified(), getHash("MD5"));
-    }
-
-    public void assertHasChangedSince(Snapshot snapshot) {
-        Snapshot now = snapshot();
-        assertTrue(now.modTime != snapshot.modTime || !Arrays.equals(now.hash, snapshot.hash));
-    }
-
-    public void assertContentsHaveNotChangedSince(Snapshot snapshot) {
-        Snapshot now = snapshot();
-        assertArrayEquals(String.format("contents of %s has changed", this), snapshot.hash, now.hash);
-    }
-
-    public void assertHasNotChangedSince(Snapshot snapshot) {
-        Snapshot now = snapshot();
-        assertEquals(String.format("last modified time of %s has changed", this), snapshot.modTime, now.modTime);
-        assertArrayEquals(String.format("contents of %s has changed", this), snapshot.hash, now.hash);
-    }
-
-    public void writeProperties(Map<?, ?> properties) {
-        Properties props = new Properties();
-        props.putAll(properties);
-        try {
-            FileOutputStream stream = new FileOutputStream(this);
-            try {
-                props.store(stream, "comment");
-            } finally {
-                stream.close();
-            }
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-    
-    public Map<String, ?> exec(Object... args) {
-        return new TestFileHelper(this).exec(args);
-    }
-
-    public class Snapshot {
-        private final long modTime;
-        private final byte[] hash;
-
-        public Snapshot(long modTime, byte[] hash) {
-            this.modTime = modTime;
-            this.hash = hash;
-        }
-    }
-}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFileContext.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFileContext.java
deleted file mode 100644
index 1e657e7..0000000
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFileContext.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.util;
-
-public interface TestFileContext {
-    TestFile getTestDir();
-
-    TestFile file(Object... path);
-}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFileHelper.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFileHelper.groovy
deleted file mode 100755
index 1a7c050..0000000
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFileHelper.groovy
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.util
-
-import java.util.zip.ZipInputStream
-import org.apache.commons.lang.StringUtils
-import org.apache.tools.ant.Project
-import org.apache.tools.ant.taskdefs.Expand
-import org.apache.tools.ant.taskdefs.Tar
-import org.apache.tools.ant.taskdefs.Untar
-import org.apache.tools.ant.taskdefs.Zip
-import static org.hamcrest.Matchers.equalTo
-import static org.junit.Assert.assertThat
-import static org.junit.Assert.assertTrue
-import org.apache.commons.io.FileUtils
-
-class TestFileHelper {
-    TestFile file
-
-    TestFileHelper(TestFile file) {
-        this.file = file
-    }
-
-    void unzipTo(File target, boolean nativeTools) {
-        // Check that each directory in hierarchy is present
-        file.withInputStream {InputStream instr ->
-            def dirs = [] as Set
-            def zipStr = new ZipInputStream(instr)
-            def entry
-            while (entry = zipStr.getNextEntry()) {
-                if (entry.directory) {
-                    assertTrue("Duplicate directory '$entry.name'", dirs.add(entry.name))
-                }
-                if (!entry.name.contains('/')) {
-                    continue
-                }
-                def parent = StringUtils.substringBeforeLast(entry.name, '/') + '/'
-                assertTrue("Missing dir '$parent'", dirs.contains(parent))
-            }
-        }
-
-        if (nativeTools && isUnix()) {
-            def process = ['unzip', '-o', file.absolutePath, '-d', target.absolutePath].execute()
-            process.consumeProcessOutput(System.out, System.err)
-            assertThat(process.waitFor(), equalTo(0))
-            return
-        }
-
-        def unzip = new Expand()
-        unzip.src = file
-        unzip.dest = target
-
-        unzip.project = new Project()
-        unzip.execute()
-    }
-
-    void untarTo(File target, boolean nativeTools) {
-        if (nativeTools && isUnix()) {
-            target.mkdirs()
-            def builder = new ProcessBuilder(['tar', '-xpf', file.absolutePath])
-            builder.directory(target)
-            def process = builder.start()
-            process.consumeProcessOutput()
-            assertThat(process.waitFor(), equalTo(0))
-            return
-        }
-
-        def untar = new Untar()
-        untar.setSrc(file)
-        untar.setDest(target)
-
-        if (file.name.endsWith(".tgz")) {
-            def method = new Untar.UntarCompressionMethod()
-            method.value = "gzip"
-            untar.compression = method
-        } else if (file.name.endsWith(".tbz2")) {
-            def method = new Untar.UntarCompressionMethod()
-            method.value = "bzip2"
-            untar.compression = method
-        }
-
-        untar.project = new Project()
-        untar.execute()
-    }
-
-    private boolean isUnix() {
-        return !System.getProperty('os.name').toLowerCase().contains('windows')
-    }
-
-    String getPermissions() {
-        def process = ["ls", "-ld", file.absolutePath].execute()
-        def result = process.inputStream.text
-        def error = process.errorStream.text
-        def retval = process.waitFor()
-        if (retval != 0) {
-            throw new RuntimeException("Could not list permissions for '$file': $error")
-        }
-        def perms = result.split()[0]
-        assert perms.matches("[d\\-][rwx\\-]{9}[@\\+]?")
-        return perms.substring(1, 10)
-    }
-
-    void setPermissions(String permissions) {
-        if (!isUnix()) {
-            return
-        }
-        int m = toMode(permissions)
-        setMode(m)
-    }
-
-    void setMode(int mode) {
-        def process = ["chmod", Integer.toOctalString(mode), file.absolutePath].execute()
-        def error = process.errorStream.text
-        def retval = process.waitFor()
-        if (retval != 0) {
-            throw new RuntimeException("Could not set permissions for '$file': $error")
-        }
-    }
-
-    private int toMode(String permissions) {
-        int m = [6, 3, 0].inject(0) { mode, pos ->
-            mode |= permissions[9 - pos - 3] == 'r' ? 4 << pos : 0
-            mode |= permissions[9 - pos - 2] == 'w' ? 2 << pos : 0
-            mode |= permissions[9 - pos - 1] == 'x' ? 1 << pos : 0
-            return mode
-        }
-        return m
-    }
-
-    int getMode() {
-        return toMode(getPermissions())
-    }
-
-    void delete(boolean nativeTools) {
-        if (isUnix() && nativeTools) {
-            def process = ["rm", "-rf", file.absolutePath].execute()
-            def error = process.errorStream.text
-            def retval = process.waitFor()
-            if (retval != 0) {
-                throw new RuntimeException("Could not delete '$file': $error")
-            }
-        } else {
-            FileUtils.deleteQuietly(file);
-        }
-    }
-
-    String readLink() {
-        def process = ["readlink", file.absolutePath].execute()
-        def error = process.errorStream.text
-        def retval = process.waitFor()
-        if (retval != 0) {
-            throw new RuntimeException("Could not read link '$file': $error")
-        }
-        return process.inputStream.text.trim()
-    }
-
-    Map<String, ?> exec(Object... args) {
-        def process = ([file.absolutePath] + (args as List)).execute()
-        def output = process.inputStream.text
-        def error = process.errorStream.text
-        if (process.waitFor() != 0) {
-            throw new RuntimeException("Could not execute $file. Error: $error, Output: $output")
-        }
-        return [out: output, error: error]
-    }
-
-    public void zipTo(TestFile zipFile, boolean nativeTools) {
-        if (nativeTools && isUnix()) {
-            def process = ['zip', zipFile.absolutePath, "-r", file.name].execute(null, zipFile.parentFile)
-            process.consumeProcessOutput(System.out, System.err)
-            assertThat(process.waitFor(), equalTo(0))
-        } else {
-            Zip zip = new Zip();
-            zip.setBasedir(file);
-            zip.setDestFile(zipFile);
-            zip.setProject(new Project());
-            def whenEmpty = new Zip.WhenEmpty()
-            whenEmpty.setValue("create")
-            zip.setWhenempty(whenEmpty);
-            zip.execute();
-        }
-    }
-
-    public void tarTo(TestFile tarFile, boolean nativeTools) {
-        if (nativeTools && isUnix()) {
-            def process = ['tar', "-cf", tarFile.absolutePath, file.name].execute(null, tarFile.parentFile)
-            process.consumeProcessOutput(System.out, System.err)
-            assertThat(process.waitFor(), equalTo(0))
-        } else {
-            Tar tar = new Tar();
-            tar.setBasedir(file);
-            tar.setDestFile(tarFile);
-            tar.setProject(new Project())
-            tar.execute()
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy
index 5614ddc..ea278b2 100755
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy
@@ -87,6 +87,9 @@ enum TestPrecondition {
     JDK7({
         System.getProperty("java.version").startsWith("1.7")
     }),
+    NOT_JDK5({
+        !JDK5.fulfilled
+    }),
     NOT_JDK7({
         !JDK7.fulfilled
     }),
diff --git a/subprojects/internal-testing/src/test/groovy/org/gradle/test/fixtures/concurrent/ConcurrentSpecTest.groovy b/subprojects/internal-testing/src/test/groovy/org/gradle/test/fixtures/concurrent/ConcurrentSpecTest.groovy
new file mode 100644
index 0000000..aa989a1
--- /dev/null
+++ b/subprojects/internal-testing/src/test/groovy/org/gradle/test/fixtures/concurrent/ConcurrentSpecTest.groovy
@@ -0,0 +1,350 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.concurrent
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
+
+class ConcurrentSpecTest extends ConcurrentSpec {
+    def "can use instants to test that an action happens asynchronously"() {
+        Worker worker = new Worker(executor)
+
+        given:
+        def action = {
+            thread.block()
+            instant.actionCompleted
+        }
+
+        when:
+        async {
+            worker.runLater(action)
+            instant.queued
+        }
+
+        then:
+        instant.queued < instant.actionCompleted
+    }
+
+    def "can use instants to test that an action happens while another is blocked"() {
+        Worker worker = new Worker(executor)
+
+        given:
+        def action1 = {
+            thread.blockUntil.action2Completed
+            instant.action1Completed
+        }
+        def action2 = {
+            instant.action2Completed
+        }
+
+        when:
+        async {
+            worker.runLater(action1)
+            thread.block()
+            worker.runLater(action2)
+        }
+
+        then:
+        instant.action2Completed < instant.action1Completed
+    }
+
+    def "can use instants to test that a method blocks until action is complete"() {
+        Worker worker = new Worker(executor)
+
+        given:
+        def action = {
+            thread.block()
+            instant.actionCompleted
+        }
+
+        when:
+        async {
+            worker.runLater(action)
+            worker.stop()
+            instant.stopped
+        }
+
+        then:
+        instant.stopped > instant.actionCompleted
+    }
+
+    def "can use operation to test that a method blocks until action is complete"() {
+        Worker worker = new Worker(executor)
+
+        given:
+        def action = {
+            thread.block()
+            instant.actionCompleted
+        }
+
+        when:
+        operation.runAndWait {
+            worker.runLater(action)
+            worker.stop()
+        }
+
+        then:
+        operation.runAndWait.end > instant.actionCompleted
+    }
+
+    def "can use instants to test that method blocks for a certain time"() {
+        Worker worker = new Worker(executor)
+
+        given:
+        def action = {
+            thread.blockUntil.end
+        }
+
+        when:
+        async {
+            instant.start
+            worker.run(action, 2)
+            instant.end
+        }
+
+        then:
+        instant.end - instant.start in approx(2000)
+    }
+
+    def "can use operation to test that method blocks for a certain time"() {
+        Worker worker = new Worker(executor)
+
+        given:
+        def action = {
+            thread.blockUntil.timesOut
+        }
+
+        when:
+        operation.timesOut {
+            worker.run(action, 2)
+        }
+
+        then:
+        operation.timesOut.duration in approx(2000)
+    }
+
+    def "fails when method does not block for expected time"() {
+        Worker worker = new Worker(executor)
+
+        given:
+        def action = {
+            thread.blockUntil.runAndWait
+        }
+
+        when:
+        operation.runAndWait {
+            worker.run(action, 2)
+        }
+        def failure = null
+        try {
+            assert operation.runAndWait.duration in approx(5000)
+        } catch (AssertionError e) {
+            failure = e
+        }
+
+        then:
+        failure != null
+        failure.message.contains('operation.runAndWait.duration in approx(5000)')
+    }
+
+    def "can use instants to test that method executes one thing at a time"() {
+        Synchronizer synchronizer = new Synchronizer()
+
+        given:
+        def action1 = {
+            instant.action1Start
+            thread.block()
+            instant.action1End
+        }
+        def action2 = {
+            instant.action2Start
+        }
+
+        when:
+        start {
+            synchronizer.runNow(action1)
+        }
+        async {
+            thread.blockUntil.action1Start
+            synchronizer.runNow(action2)
+            instant.end
+        }
+
+        then:
+        instant.action2Start > instant.action1End
+        instant.end > instant.action2Start
+    }
+
+    def "cannot query instant that has not been defined in test thread"() {
+        when:
+        instant.unknown
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == "Instant 'unknown' has not been defined by any test thread."
+    }
+
+    def "cannot query operation that has not been defined in test thread"() {
+        when:
+        operation.unknown
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == "Operation 'unknown' has not been defined by any test thread."
+    }
+
+    def "cannot query operation end time while it is running"() {
+        when:
+        operation.doStuff {
+            operation.doStuff.end
+        }
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == "Operation 'doStuff' has not completed yet."
+    }
+
+    def "cannot query operation duration while it is running"() {
+        when:
+        operation.doStuff {
+            operation.doStuff.duration
+        }
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == "Operation 'doStuff' has not completed yet."
+    }
+
+    def "can use test threads to define instants outside of an async { } block"() {
+        def worker = new Worker(executor)
+
+        given:
+        def action = {
+            instant.actionExecuted
+        }
+
+        when:
+        worker.runLater(action)
+        worker.stop()
+
+        then:
+        instant.actionExecuted
+    }
+
+    def "fails when waiting for an instant and no other test threads are running"() {
+        when:
+        thread.blockUntil.unknown
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == "Cannot wait for instant 'unknown', as it has not been defined and no test threads are currently running."
+
+        when:
+        async {
+            thread.blockUntil.unknown
+        }
+
+        then:
+        e = thrown()
+        e.message == "Cannot wait for instant 'unknown', as it has not been defined and no other test threads are currently running."
+
+        when:
+        start {
+            thread.blockUntil.unknown
+        }
+        async { }
+
+        then:
+        e = thrown()
+        e.message == "Cannot wait for instant 'unknown', as it has not been defined and no other test threads are currently running."
+    }
+
+    def "async { } block rethrows test thread failures"() {
+        def worker = new Worker(executor)
+        def failure = new RuntimeException()
+
+        when:
+        async {
+            worker.runLater { throw failure }
+        }
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+    }
+
+    def "defines implicit instant for operation end point"() {
+        when:
+        operation.thing {}
+
+        then:
+        instant.thing == operation.thing.end
+    }
+
+    static class Worker {
+        final Executor executor
+        final Object lock = new Object()
+        int count
+
+        Worker(Executor executor) {
+            this.executor = executor
+        }
+
+        void run(Runnable runnable, int timeoutSeconds) {
+            def finished = new CountDownLatch(1)
+            runLater {
+                try {
+                    runnable.run()
+                } finally {
+                    finished.countDown()
+                }
+            }
+            finished.await(timeoutSeconds, TimeUnit.SECONDS)
+        }
+
+        void runLater(Runnable runnable) {
+            synchronized (lock) {
+                count++
+            }
+            executor.execute {
+                try {
+                    runnable.run()
+                } finally {
+                    synchronized (lock) {
+                        count--
+                        lock.notifyAll()
+                    }
+                }
+            }
+        }
+
+        void stop() {
+            synchronized (lock) {
+                while (count > 0) {
+                    lock.wait()
+                }
+            }
+        }
+    }
+
+    static class Synchronizer {
+        synchronized void runNow(Runnable runnable) {
+            runnable.run()
+        }
+    }
+}
diff --git a/subprojects/internal-testing/src/test/groovy/org/gradle/util/AssertionsTest.groovy b/subprojects/internal-testing/src/test/groovy/org/gradle/util/AssertionsTest.groovy
new file mode 100644
index 0000000..a85c621
--- /dev/null
+++ b/subprojects/internal-testing/src/test/groovy/org/gradle/util/AssertionsTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.util
+
+import spock.lang.Specification
+
+import static org.gradle.util.Assertions.assertThat
+
+/**
+ * by Szczepan Faber, created at: 12/18/12
+ */
+class AssertionsTest extends Specification {
+
+    static class Foo {
+        List listField;
+        Foo fooField;
+    }
+
+    def "knows if instances share state"() {
+        given:
+        def foo = new Foo()
+        def list = [1]
+
+        and:
+        def root = new Foo(listField: list, fooField: foo)
+        def equal = new Foo(listField: [1], fooField: new Foo())
+
+        and:
+        def sameList = new Foo(listField: list, fooField: new Foo())
+        def sameFoo = new Foo(listField: [1], fooField: foo)
+
+        expect:
+        assertThat(root).doesNotShareStateWith(equal)
+
+        when:
+        assertThat(root).doesNotShareStateWith(sameList)
+        then:
+        def ex = thrown(AssertionError)
+        ex.message.contains('listField')
+
+        when:
+        assertThat(root).doesNotShareStateWith(sameFoo)
+        then:
+        def ex2 = thrown(AssertionError)
+        ex2.message.contains('fooField')
+    }
+}
diff --git a/subprojects/internal-testing/src/test/groovy/org/gradle/util/TempDirIsUniquePerTestSpec.groovy b/subprojects/internal-testing/src/test/groovy/org/gradle/util/TempDirIsUniquePerTestSpec.groovy
new file mode 100644
index 0000000..5885d21
--- /dev/null
+++ b/subprojects/internal-testing/src/test/groovy/org/gradle/util/TempDirIsUniquePerTestSpec.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.util
+
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 3/14/12
+ */
+class TempDirIsUniquePerTestSpec extends Specification {
+
+    @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+    static tests = new HashSet()
+    static tmpDirs = new HashSet()
+
+    def setup() {
+        //it's very important we try to access the test dir in the setup()
+        temp.testDirectory
+    }
+    
+    def "testOne"() {
+        when:
+        tests << "testOne"
+        tmpDirs << temp.testDirectory
+        
+        then:
+        tests.size() == tmpDirs.size()
+    }
+
+    def "testTwo"() {
+        when:
+        tests << "testTwo"
+        tmpDirs << temp.testDirectory
+
+        then:
+        tests.size() == tmpDirs.size()
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyCustomPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyCustomPublishIntegrationTest.groovy
index 9d95829..9e2244d 100644
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyCustomPublishIntegrationTest.groovy
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyCustomPublishIntegrationTest.groovy
@@ -17,16 +17,21 @@
 package org.gradle.api.publish.ivy
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.file.TestFile
 
 class IvyCustomPublishIntegrationTest extends AbstractIntegrationSpec {
 
-    public void "can publish custom configurations"() {
-        given:
-        def module = ivyRepo.module("org.gradle", "publish", "2")
-        def artifact = file("artifact.txt").createFile()
+    org.gradle.test.fixtures.ivy.IvyFileModule module
+    TestFile artifact
 
+    def setup() {
+        module = ivyRepo.module("org.gradle", "publish", "2")
+        artifact = file("artifact.txt").createFile()
         settingsFile << 'rootProject.name = "publish"'
+    }
 
+    public void "can publish custom configurations with java plugin"() {
+        given:
         buildFile << """
             apply plugin: 'java'
             apply plugin: 'ivy-publish'
@@ -64,4 +69,43 @@ class IvyCustomPublishIntegrationTest extends AbstractIntegrationSpec {
         }
     }
 
+    public void "can publish custom configurations"() {
+        given:
+        buildFile << """
+            apply plugin: 'base'
+            apply plugin: 'ivy-publish'
+
+            version = '2'
+            group = 'org.gradle'
+
+            configurations { custom }
+            artifacts {
+                custom file("${artifact.name}"), {
+                    name "foo"
+                    extension "txt"
+                }
+            }
+
+            publishing {
+                repositories {
+                    ivy {
+                        url "${ivyRepo.uri}"
+                    }
+                }
+            }
+        """
+
+        when:
+        succeeds 'publish'
+
+        then:
+        module.ivyFile.assertIsFile()
+        module.assertArtifactsPublished("ivy-2.xml", "foo-2.txt")
+        with(module.ivy.artifacts.foo) {
+            name == "foo"
+            ext == "txt"
+            "custom" in conf
+        }
+    }
+
 }
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyHttpPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyHttpPublishIntegrationTest.groovy
index e9c4c2c..8733309 100644
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyHttpPublishIntegrationTest.groovy
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyHttpPublishIntegrationTest.groovy
@@ -17,12 +17,12 @@
 package org.gradle.api.publish.ivy
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.ProgressLoggingFixture
+import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
+import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.ivy.IvyFileModule
 import org.gradle.test.fixtures.server.http.HttpServer
 import org.gradle.util.GradleVersion
 import org.gradle.util.Jvm
-import org.gradle.util.TestFile
 import org.gradle.util.TextUtil
 import org.hamcrest.Matchers
 import org.junit.Rule
@@ -169,7 +169,7 @@ credentials {
 
         and:
         failure.assertHasDescription('Execution failed for task \':publishIvyPublicationToIvyRepository\'.')
-        failure.assertHasCause('Could not publish configurations: [archives, compile, default, runtime]')
+        failure.assertHasCause('Failed to publish publication \'ivy\' to repository \'ivy\'')
         failure.assertThatCause(Matchers.containsString('Received status code 401 from server: Unauthorized'))
 
         where:
@@ -206,7 +206,7 @@ credentials {
 
         and:
         failure.assertHasDescription('Execution failed for task \':publishIvyPublicationToIvyRepository\'.')
-        failure.assertHasCause('Could not publish configurations: [archives, compile, default, runtime]')
+        failure.assertHasCause('Failed to publish publication \'ivy\' to repository \'ivy\'')
         failure.assertThatCause(Matchers.containsString('Received status code 500 from server: broken'))
 
         when:
@@ -217,7 +217,7 @@ credentials {
 
         and:
         failure.assertHasDescription('Execution failed for task \':publishIvyPublicationToIvyRepository\'.')
-        failure.assertHasCause('Could not publish configurations: [archives, compile, default, runtime]')
+        failure.assertHasCause('Failed to publish publication \'ivy\' to repository \'ivy\'')
         failure.assertHasCause("org.apache.http.conn.HttpHostConnectException: Connection to ${repositoryUrl} refused")
     }
 
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyLocalPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyLocalPublishIntegrationTest.groovy
index 978fa46..a0f25cb 100644
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyLocalPublishIntegrationTest.groovy
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyLocalPublishIntegrationTest.groovy
@@ -19,8 +19,8 @@ package org.gradle.api.publish.ivy
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.spockframework.util.TextUtil
-import spock.lang.Ignore
 import spock.lang.Issue
+import org.gradle.test.fixtures.ivy.IvyDescriptor
 
 public class IvyLocalPublishIntegrationTest extends AbstractIntegrationSpec {
     public void canPublishToLocalFileRepository() {
@@ -92,37 +92,86 @@ public class IvyLocalPublishIntegrationTest extends AbstractIntegrationSpec {
         shaOneFile.text == "00e14c6ef59816760e2c9b5a57157e8ac9de4012"
     }
 
-    @Ignore("There's no real parallel of this with the new publication mechanism. Eventually, descriptor generation will be a standalone task")
     @Issue("GRADLE-1811")
     public void canGenerateTheIvyXmlWithoutPublishing() {
-        //this is more like documenting the current behavior.
-        //Down the road we should add explicit task to create ivy.xml file
-
         given:
-        buildFile << '''
-            apply plugin: 'java'
+        def module = ivyRepo.module("org.gradle", "generateIvy", "2")
 
-            configurations {
-              myJars
-            }
+        settingsFile << "rootProject.name = 'generateIvy'"
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
 
-            task myJar(type: Jar)
+            version = '2'
+            group = 'org.gradle'
 
-            artifacts {
-              'myJars' myJar
+            publishing {
+                repositories {
+                    ivy {
+                        url "${ivyRepo.uri}"
+                    }
+                }
             }
 
-            task ivyXml(type: Upload) {
-              descriptorDestination = file('ivy.xml')
-              uploadDescriptor = true
-              configuration = configurations.myJars
+            generateIvyModuleDescriptor {
+                destination = 'generated-ivy.xml'
             }
-        '''
+        """
 
         when:
-        succeeds 'ivyXml'
+        succeeds 'generateIvyModuleDescriptor'
 
         then:
-        file('ivy.xml').assertIsFile()
+        file('generated-ivy.xml').assertIsFile()
+        IvyDescriptor ivy = new IvyDescriptor(file('generated-ivy.xml'))
+        with (ivy.artifacts['generateIvy']) {
+            name == 'generateIvy'
+            ext == 'jar'
+            conf == ['archives', 'runtime']
+        }
+
+        and:
+        module.ivyFile.assertDoesNotExist()
     }
+
+    def "can publish with non-ascii characters"() {
+        def organisation = 'group-√æず'
+        def moduleName = 'artifact-∫ʙぴ'
+        def version = 'version-₦ガき∆'
+        def description = 'description-ç√∫'
+
+        given:
+        settingsFile << "rootProject.name = '${moduleName}'"
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            group = '${organisation}'
+            version = '${version}'
+
+            publishing {
+                repositories {
+                    ivy { url "${ivyRepo.uri}" }
+                }
+                publications {
+                    ivy {
+                        descriptor.withXml {
+                            asNode().info[0].appendNode('description', "${description}")
+                        }
+                    }
+                }
+            }
+        """
+        when:
+        succeeds 'publish'
+
+        then:
+        def ivy = ivyRepo.module(organisation, moduleName, version).ivy
+        ivy.organisation == organisation
+        ivy.module == moduleName
+        ivy.revision == version
+        ivy.description == description
+    }
+
+
 }
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorModificationIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorModificationIntegTest.groovy
index fdafb45..1c8f0eb 100644
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorModificationIntegTest.groovy
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorModificationIntegTest.groovy
@@ -16,7 +16,6 @@
 
 package org.gradle.api.publish.ivy
 
-import groovy.util.slurpersupport.GPathResult
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 
 class IvyPublishDescriptorModificationIntegTest extends AbstractIntegrationSpec {
@@ -51,7 +50,7 @@ class IvyPublishDescriptorModificationIntegTest extends AbstractIntegrationSpec
         ":jar" in executedTasks
 
         and:
-        asXml(module.ivyFile).info[0]. at revision == "2"
+        module.ivy.revision == "2"
 
         when:
         buildFile << """
@@ -75,11 +74,30 @@ class IvyPublishDescriptorModificationIntegTest extends AbstractIntegrationSpec
 
         and:
         // Note that the modified “coordinates” do not affect how the module is published
-        // This is intentional
-        asXml(module.ivyFile).info[0]. at revision == "3"
+        // This is not the desired behaviour and will be fixed in the future so that XML modification changes the publication model consistently.
+        module.ivy.revision == "3"
     }
 
-    GPathResult asXml(File file) {
-        new XmlSlurper().parse(file)
+    def "produces sensible error when withXML fails"() {
+        when:
+        buildFile << """
+            publishing {
+                publications {
+                    ivy {
+                        descriptor {
+                            withXml {
+                                asNode().foo = "3"
+                            }
+                        }
+                    }
+                }
+            }
+        """
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':generateIvyModuleDescriptor'")
+        failure.assertHasCause("Could not apply withXml() to Ivy module descriptor")
+        failure.assertHasCause("No such property: foo for class: groovy.util.Node")
     }
 }
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultipleReposIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultipleReposIntegrationTest.groovy
index 34c9dc6..f12a35b 100644
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultipleReposIntegrationTest.groovy
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultipleReposIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.api.publish.ivy
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.ProgressLoggingFixture
+import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
 import org.gradle.test.fixtures.ivy.IvyFileRepository
 import org.gradle.test.fixtures.ivy.IvyModule
 import org.gradle.test.fixtures.server.http.HttpServer
@@ -51,7 +51,7 @@ class IvyPublishMultipleReposIntegrationTest extends AbstractIntegrationSpec {
             publishing {
                 publications {
                     ivy.descriptor.withXml {
-                        asNode(). at rev = 10
+                        asNode().info[0].appendNode('description', 'test module')
                     }
                 }
                 repositories {
@@ -64,15 +64,6 @@ class IvyPublishMultipleReposIntegrationTest extends AbstractIntegrationSpec {
                     }
                 }
             }
-
-            // Be nasty and delete the descriptor after the first publishing
-            // to make sure it's regenerated for the second publish
-            publishIvyPublicationToIvyRepository {
-                doLast {
-                    assert publication.descriptor.file.delete()
-                    publication.descriptor.withXml { asNode(). at rev = "11" }
-                }
-            }
         """
 
         when:
@@ -89,8 +80,8 @@ class IvyPublishMultipleReposIntegrationTest extends AbstractIntegrationSpec {
         repo2Module.jarFile.exists()
 
         and: // Modification applied to both
-        repo1Module.ivy.rev == "10"
-        repo2Module.ivy.rev == "11"
+        repo1Module.ivy.description == "test module"
+        repo2Module.ivy.description == "test module"
     }
 
 }
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvySFtpPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvySFtpPublishIntegrationTest.groovy
deleted file mode 100644
index ad8bf6f..0000000
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvySFtpPublishIntegrationTest.groovy
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package org.gradle.api.publish.ivy
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.ProgressLoggingFixture
-import org.gradle.test.fixtures.server.sftp.SFTPServer
-import org.junit.Rule
-import spock.lang.Ignore
-
- at Ignore("New publishing requires IvyArtifactRepository, which doesn't allow custom resolvers")
-class IvySFtpPublishIntegrationTest extends AbstractIntegrationSpec {
-
-    @Rule
-    public final SFTPServer sftpServer = new SFTPServer(distribution.temporaryFolder)
-    @Rule
-    ProgressLoggingFixture progressLogging
-
-    public void "can publish using SftpResolver"() {
-        given:
-        file("settings.gradle") << 'rootProject.name = "publish"'
-
-        and:
-        buildFile << """
-        apply plugin: 'java'
-        version = '2'
-        group = 'org.gradle'
-
-        publishing {
-            repositories {
-                add(new org.apache.ivy.plugins.resolver.SFTPResolver()) {
-                    addArtifactPattern "repos/libs/[organisation]/[module]/[artifact]-[revision].[ext]"
-                    host = "${sftpServer.hostAddress}"
-                    port = ${sftpServer.port}
-                    user = "user"
-                    userPassword = "user"
-                }
-            }
-        }
-        """
-        when:
-
-        run "uploadArchives"
-        then:
-        sftpServer.hasFile("repos/libs/org.gradle/publish/publish-2.jar")
-        sftpServer.hasFile("repos/libs/org.gradle/publish/ivy-2.xml");
-        sftpServer.file("repos/libs/org.gradle/publish/publish-2.jar").assertIsCopyOf(file('build/libs/publish-2.jar'))
-        and:
-        progressLogging.uploadProgressLogged("repos/libs/org.gradle/publish/ivy-2.xml")
-        progressLogging.uploadProgressLogged("repos/libs/org.gradle/publish/publish-2.jar")
-    }
-
-    public void "reports Authentication Errors"() {
-        given:
-        file("settings.gradle") << 'rootProject.name = "publish"'
-
-        and:
-        buildFile << """
-        apply plugin: 'java'
-        version = '2'
-        group = 'org.gradle'
-        uploadArchives {
-            repositories {
-                add(new org.apache.ivy.plugins.resolver.SFTPResolver()) {
-                    addArtifactPattern "repos/libs/[organisation]/[module]/[artifact]-[revision].[ext]"
-                    host = "${sftpServer.hostAddress}"
-                    port = ${sftpServer.port}
-                    user = "simple"
-                    userPassword = "wrongPassword"
-                }
-            }
-        }
-        """
-        when:
-        fails "uploadArchives"
-
-        then:
-        failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
-        failure.assertHasCause('Could not publish configuration \':archives\'.')
-        failure.assertHasCause("java.io.IOException: Auth fail")
-    }
-}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy
index 91dab6f..59260d4 100644
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy
@@ -13,13 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
-
 package org.gradle.api.publish.ivy
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import spock.lang.Ignore
 
 class IvySingleProjectPublishIntegrationTest extends AbstractIntegrationSpec {
 
@@ -77,61 +73,4 @@ class IvySingleProjectPublishIntegrationTest extends AbstractIntegrationSpec {
         ivyDescriptor.expectArtifact("jar1").conf == ["archives", "toPublish"]
         ivyDescriptor.expectArtifact("jar2").conf == ["toPublish"]
     }
-
-    @Ignore("We don't have a way to have separate publications right now")
-    def "publish multiple artifacts in separate configurations"() {
-        file("settings.gradle") << "rootProject.name = 'publishTest'"
-        file("file1") << "some content"
-        file("file2") << "other content"
-
-        buildFile << """
-            apply plugin: "base"
-
-            group = "org.gradle.test"
-            version = 1.9
-
-            configurations { publish1; publish2 }
-
-            task jar1(type: Jar) {
-                baseName = "jar1"
-                from "file1"
-            }
-
-            task jar2(type: Jar) {
-                baseName = "jar2"
-                from "file2"
-            }
-
-            artifacts {
-                publish1 jar1
-                publish2 jar2
-            }
-
-            tasks.withType(Upload) {
-                repositories {
-                    main {
-                        url "${ivyRepo.uri}"
-                    }
-                }
-            }
-        """
-
-        when:
-        run "uploadPublish$n"
-
-        then:
-        def ivyModule = ivyRepo.module("org.gradle.test", "publishTest", "1.9")
-        ivyModule.assertArtifactsPublished("ivy-1.9.xml", "jar$n-1.9.jar")
-        ivyModule.moduleDir.file("jar$n-1.9.jar").bytes == file("build/libs/jar$n-1.9.jar").bytes
-
-        and:
-        def ivyDescriptor = ivyModule.ivy
-        ivyDescriptor.expectArtifact("jar$n").conf.contains("publish$n" as String)
-        ivyDescriptor.expectArtifact("jar$n").conf.contains("archives") == onArchivesConfig
-
-        where:
-        n | onArchivesConfig
-        1 | true
-        2 | false
-    }
 }
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/SamplesIvyPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/SamplesIvyPublishIntegrationTest.groovy
index 0c43953..f0be18b 100644
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/SamplesIvyPublishIntegrationTest.groovy
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/SamplesIvyPublishIntegrationTest.groovy
@@ -18,52 +18,32 @@ package org.gradle.api.publish.ivy
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.ivy.IvyDescriptor
-import org.gradle.test.fixtures.ivy.IvyFileRepository
-import org.gradle.test.fixtures.ivy.IvyHttpRepository
-import org.gradle.test.fixtures.ivy.IvyModule
-import org.gradle.test.fixtures.server.http.HttpServer
-import org.gradle.util.TestFile
 import org.gradle.util.TextUtil
 import org.junit.Rule
 
 public class SamplesIvyPublishIntegrationTest extends AbstractIntegrationSpec {
 
     @Rule Sample sample = new Sample("ivypublish-new")
-    @Rule HttpServer httpServer
 
     def sample() {
         given:
-        httpServer.start()
         executer.inDirectory(sample.dir)
 
         and:
-        def fileRepo = new IvyFileRepository(new TestFile(sample.dir, "repo"))
-        IvyHttpRepository httpRepo = new IvyHttpRepository(httpServer, fileRepo)
-        IvyModule ivyModule = httpRepo.module("org.gradle.test", "ivypublish", "1.0")
-        def uploads = file("uploads").createDir()
-        ivyModule.expectPut("user1", "secret", uploads,
-                "ivypublish-1.0.jar", "ivypublish-1.0.jar.sha1",
-                "ivypublishSource-1.0-src.jar", "ivypublishSource-1.0-src.jar.sha1",
-                "ivy-1.0.xml", "ivy-1.0.xml.sha1"
-        )
-
-        and:
-        sample.dir.file("build.gradle") << """
-            publishing.repositories.ivy.url = "${httpRepo.uri}"
-        """
+        def fileRepo = ivy(sample.dir.file("build/repo"))
+        def ivyModule = fileRepo.module("org.gradle.test", "ivypublish", "1.0")
 
         when:
         succeeds "publish"
 
         then:
-        def uploadedDescriptor = uploads.file("ivy-1.0.xml")
-        IvyDescriptor ivy = new IvyDescriptor(uploadedDescriptor)
+        IvyDescriptor ivy = ivyModule.ivy
         ivy.artifacts.ivypublishSource.mavenAttributes.classifier == "src"
-        ivy.configurations.keySet() == ['archives', 'compile', 'default', 'runtime', 'testCompile', 'testRuntime'] as Set
+        ivy.configurations.keySet() == ['archives', 'compile', 'default', 'runtime'] as Set
         ivy.dependencies.compile.assertDependsOn('junit', 'junit', '4.10')
         ivy.dependencies.compile.assertDependsOn('ivypublish', 'subproject', 'unspecified')
 
-        def actualIvyXmlText = uploadedDescriptor.text.replaceFirst('publication="\\d+"', 'publication="«PUBLICATION-TIME-STAMP»"').trim()
+        def actualIvyXmlText = ivyModule.ivyFile.text.replaceFirst('publication="\\d+"', 'publication="«PUBLICATION-TIME-STAMP»"').trim()
         actualIvyXmlText == expectedIvyOutput
     }
 
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyModuleDescriptor.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyModuleDescriptor.java
index 3dfef9f..0775fad 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyModuleDescriptor.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyModuleDescriptor.java
@@ -21,8 +21,6 @@ import org.gradle.api.Incubating;
 import org.gradle.api.XmlProvider;
 import org.gradle.api.internal.HasInternalProtocol;
 
-import java.io.File;
-
 /**
  * The descriptor of any Ivy publication.
  * <p>
@@ -64,26 +62,11 @@ public interface IvyModuleDescriptor {
      * For details on the structure of the XML to be modified, see <a href="http://ant.apache.org/ivy/history/latest-milestone/ivyfile.html">the
      * Ivy Module Descriptor reference</a>.
      *
+     *
      * @param action The configuration action.
      * @see IvyPublication
      * @see XmlProvider
      */
-    void withXml(Action<XmlProvider> action);
-
-    /**
-     * The generated descriptor file.
-     *
-     * This file will only exist <b>after</b> the publishing task that publishing the publication this descriptor is part of.
-     *
-     * @return The generated descriptor file
-     */
-    File getFile();
-
-    /**
-     * Sets where the descriptor file should be generated.
-     *
-     * @param descriptorFile The new location to generate the descriptor to
-     */
-    void setFile(File descriptorFile);
+    void withXml(Action<? super XmlProvider> action);
 
 }
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyPublication.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyPublication.java
index 3fa0313..f3cbc00 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyPublication.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyPublication.java
@@ -77,6 +77,20 @@ import org.gradle.api.publish.Publication;
  * All {@link org.gradle.api.publish.ivy.tasks.PublishToIvyRepository} tasks added by this plugin automatically become dependencies of this
  * lifecycle task, which means that often the most convenient way to publish your project is to just run the “{@code publish}” task.
  *
+ * <h4>Generating the ivy module descriptor</h4>
+ *
+ * A {@link org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor} task will be created for each {@code IvyPublication} in {@code publishing.publications}.
+ * Each {@code GenerateIvyTask} is automatically a dependency of the respective {@code PublishToIvyRepository} task, so this task is only required for
+ * generating the ivy.xml file without also publishing your module.
+ *
+ * <pre autoTested="true">
+ * apply plugin: 'ivy-publish'
+
+ * generateIvyModuleDescriptor {
+ *     destination = file('generated-ivy.xml') // Override the default file that will contain the descriptor
+ * }
+ * </pre>
+ *
  * @since 1.3
  */
 @Incubating
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyModuleDescriptor.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyModuleDescriptor.java
index 5559017..923701e 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyModuleDescriptor.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyModuleDescriptor.java
@@ -17,29 +17,37 @@
 package org.gradle.api.publish.ivy.internal;
 
 import org.gradle.api.Action;
+import org.gradle.api.internal.UserCodeAction;
 import org.gradle.api.XmlProvider;
-import org.gradle.api.internal.XmlTransformer;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+import org.gradle.listener.ActionBroadcast;
 
-import java.io.File;
+import java.util.Set;
 
 public class DefaultIvyModuleDescriptor implements IvyModuleDescriptorInternal {
 
-    private final XmlTransformer transformer = new XmlTransformer();
-    private File file;
+    private final ActionBroadcast<XmlProvider> xmlActions = new ActionBroadcast<XmlProvider>();
+    private final IvyPublicationInternal ivyPublication;
 
-    public void withXml(Action<XmlProvider> action) {
-        transformer.addAction(action);
+    public DefaultIvyModuleDescriptor(IvyPublicationInternal ivyPublication) {
+        this.ivyPublication = ivyPublication;
     }
 
-    public File getFile() {
-        return file;
+    public Module getModule() {
+        return ivyPublication.getModule();
     }
 
-    public void setFile(File descriptorFile) {
-        this.file = descriptorFile;
+    public Set<? extends Configuration> getConfigurations() {
+        return ivyPublication.asNormalisedPublication().getConfigurations();
     }
 
-    public XmlTransformer getTransformer() {
-        return transformer;
+    public void withXml(Action<? super XmlProvider> action) {
+        xmlActions.add(new UserCodeAction<XmlProvider>("Could not apply withXml() to Ivy module descriptor", action));
     }
+
+    public Action<XmlProvider> getXmlAction() {
+        return xmlActions;
+    }
+
 }
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyPublication.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyPublication.java
index 54543fb..4accafd 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyPublication.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyPublication.java
@@ -19,17 +19,21 @@ package org.gradle.api.publish.ivy.internal;
 import org.gradle.api.Action;
 import org.gradle.api.Transformer;
 import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.file.ConfigurableFileCollection;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
 import org.gradle.api.internal.tasks.TaskResolver;
 import org.gradle.api.publish.ivy.IvyModuleDescriptor;
-import org.gradle.api.tasks.TaskDependency;
 import org.gradle.internal.reflect.Instantiator;
 
-import java.util.HashSet;
+import java.io.File;
 import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.Callable;
 
 import static org.gradle.util.CollectionUtils.*;
 
@@ -41,17 +45,18 @@ public class DefaultIvyPublication implements IvyPublicationInternal {
     private final Set<? extends Configuration> configurations;
     private final FileResolver fileResolver;
     private final TaskResolver taskResolver;
+    private PublishArtifact descriptorArtifact;
 
     public DefaultIvyPublication(
             String name, Instantiator instantiator, Set<? extends Configuration> configurations,
             DependencyMetaDataProvider dependencyMetaDataProvider, FileResolver fileResolver, TaskResolver taskResolver
     ) {
         this.name = name;
-        this.descriptor = instantiator.newInstance(DefaultIvyModuleDescriptor.class);
         this.configurations = configurations;
         this.dependencyMetaDataProvider = dependencyMetaDataProvider;
         this.fileResolver = fileResolver;
         this.taskResolver = taskResolver;
+        this.descriptor = instantiator.newInstance(DefaultIvyModuleDescriptor.class, this);
     }
 
     public String getName() {
@@ -67,30 +72,55 @@ public class DefaultIvyPublication implements IvyPublicationInternal {
     }
 
     public FileCollection getPublishableFiles() {
-        return new DefaultConfigurableFileCollection(
-                "publication artifacts", fileResolver, taskResolver,
-                collect(configurations, new Transformer<FileCollection, Configuration>() {
+        ConfigurableFileCollection files = new DefaultConfigurableFileCollection("publication artifacts", fileResolver, taskResolver);
+        files.from(new Callable<Set<FileCollection>>() {
+            public Set<FileCollection> call() throws Exception {
+                return collect(getConfigurations(), new Transformer<FileCollection, Configuration>() {
                     public FileCollection transform(Configuration configuration) {
                         return configuration.getAllArtifacts().getFiles();
                     }
-                }));
+                });
+            }
+        });
+        if (descriptorArtifact != null) {
+            files.from(new Callable<File>() {
+                public File call() throws Exception {
+                    return getDescriptorFile();
+                }
+            });
+            files.builtBy(descriptorArtifact);
+        }
+        return files;
     }
 
-    public TaskDependency getBuildDependencies() {
-        return getPublishableFiles().getBuildDependencies();
+    public IvyNormalizedPublication asNormalisedPublication() {
+        return new IvyNormalizedPublication(getModule(), getFlattenedConfigurations(), getDescriptorFile());
     }
 
-    public IvyNormalizedPublication asNormalisedPublication() {
-        return new IvyNormalizedPublication(dependencyMetaDataProvider.getModule(), getFlattenedConfigurations(), descriptor.getFile(), descriptor.getTransformer());
+    public Module getModule() {
+        return dependencyMetaDataProvider.getModule();
     }
 
     public Class<IvyNormalizedPublication> getNormalisedPublicationType() {
         return IvyNormalizedPublication.class;
     }
 
+    public Set<? extends Configuration> getConfigurations() {
+        return configurations;
+    }
+
+    public void setDescriptorArtifact(PublishArtifact descriptorArtifact) {
+        this.descriptorArtifact = descriptorArtifact;
+    }
+
+    private File getDescriptorFile() {
+        return descriptorArtifact.getFile();
+    }
+
     // Flattens each of the given configurations to include any parents, visible or not.
     private Set<Configuration> getFlattenedConfigurations() {
-        return inject(new HashSet<Configuration>(), configurations, new Action<InjectionStep<Set<Configuration>, Configuration>>() {
+        Set<Configuration> flattenedConfigurations = new TreeSet<Configuration>(new Namer.Comparator<Configuration>(new Configuration.Namer()));
+        return inject(flattenedConfigurations, configurations, new Action<InjectionStep<Set<Configuration>, Configuration>>() {
             public void execute(InjectionStep<Set<Configuration>, Configuration> step) {
                 step.getTarget().addAll(step.getItem().getHierarchy());
             }
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyModuleDescriptorInternal.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyModuleDescriptorInternal.java
index bcfb751..050a564 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyModuleDescriptorInternal.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyModuleDescriptorInternal.java
@@ -16,11 +16,20 @@
 
 package org.gradle.api.publish.ivy.internal;
 
-import org.gradle.api.internal.XmlTransformer;
+import org.gradle.api.Action;
+import org.gradle.api.XmlProvider;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
 import org.gradle.api.publish.ivy.IvyModuleDescriptor;
 
+import java.util.Set;
+
 public interface IvyModuleDescriptorInternal extends IvyModuleDescriptor {
 
-    XmlTransformer getTransformer();
+    public Module getModule();
+
+    public Set<? extends Configuration> getConfigurations();
+
+    Action<XmlProvider> getXmlAction();
 
 }
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyNormalizedPublication.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyNormalizedPublication.java
new file mode 100644
index 0000000..0fca0a4
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyNormalizedPublication.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+
+import java.io.File;
+import java.util.Set;
+
+public class IvyNormalizedPublication {
+
+    private final Module module;
+    private final File descriptorFile;
+    private final Set<? extends Configuration> configurations;
+
+    public IvyNormalizedPublication(Module module, Set<? extends Configuration> configurations, File descriptorFile) {
+        this.module = module;
+        this.configurations = configurations;
+        this.descriptorFile = descriptorFile;
+    }
+
+    public Module getModule() {
+        return module;
+    }
+
+    public Set<? extends Configuration> getConfigurations() {
+        return configurations;
+    }
+
+    public File getDescriptorFile() {
+        return descriptorFile;
+    }
+
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublicationInternal.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublicationInternal.java
index b2fbb1e..dd0eb78 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublicationInternal.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublicationInternal.java
@@ -16,16 +16,25 @@
 
 package org.gradle.api.publish.ivy.internal;
 
-import org.gradle.api.Buildable;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.publish.ivy.IvyPublication;
 
-public interface IvyPublicationInternal extends IvyPublication, Buildable {
+import java.util.Set;
+
+public interface IvyPublicationInternal extends IvyPublication {
 
     IvyModuleDescriptorInternal getDescriptor();
 
     FileCollection getPublishableFiles();
 
+    Module getModule();
+
     IvyNormalizedPublication asNormalisedPublication();
 
+    Set<? extends Configuration> getConfigurations();
+
+    void setDescriptorArtifact(PublishArtifact descriptorArtifact);
 }
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublisher.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublisher.java
new file mode 100644
index 0000000..f1394d9
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublisher.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
+import org.gradle.api.internal.Cast;
+import org.gradle.api.internal.artifacts.ArtifactPublisher;
+import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
+
+import java.util.Collections;
+
+public class IvyPublisher {
+
+    private final ArtifactPublisher artifactPublisher;
+
+    public IvyPublisher(ArtifactPublisher artifactPublisher) {
+        this.artifactPublisher = artifactPublisher;
+    }
+
+    public void publish(IvyNormalizedPublication publication, IvyArtifactRepository repository) {
+        DependencyResolver dependencyResolver = Cast.cast(ArtifactRepositoryInternal.class, repository).createResolver();
+        artifactPublisher.publish(Collections.singleton(dependencyResolver), publication.getModule(), publication.getConfigurations(), publication.getDescriptorFile());
+    }
+
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/plugins/IvyPublishPlugin.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/plugins/IvyPublishPlugin.java
index c57fc04..c6b732f 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/plugins/IvyPublishPlugin.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/plugins/IvyPublishPlugin.java
@@ -21,23 +21,20 @@ import org.gradle.api.Plugin;
 import org.gradle.api.Project;
 import org.gradle.api.Task;
 import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.internal.ConventionMapping;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.publish.PublishingExtension;
 import org.gradle.api.publish.ivy.IvyPublication;
 import org.gradle.api.publish.ivy.internal.DefaultIvyPublication;
-import org.gradle.api.publish.ivy.internal.IvyModuleDescriptorInternal;
+import org.gradle.api.publish.ivy.tasks.internal.IvyPublicationDynamicDescriptorGenerationTaskCreator;
 import org.gradle.api.publish.ivy.tasks.internal.IvyPublishDynamicTaskCreator;
 import org.gradle.api.publish.plugins.PublishingPlugin;
 import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.TaskContainer;
 import org.gradle.internal.reflect.Instantiator;
 
 import javax.inject.Inject;
-import java.io.File;
 import java.util.Set;
-import java.util.concurrent.Callable;
 
 /**
  * Configures the project to publish a “main” IvyPublication to a “main” IvyArtifactRepository.
@@ -75,27 +72,22 @@ public class IvyPublishPlugin implements Plugin<Project> {
         });
         extension.getPublications().add(createPublication("ivy", project, visibleConfigurations));
 
+        TaskContainer tasks = project.getTasks();
+
+        // Create generate descriptor tasks
+        IvyPublicationDynamicDescriptorGenerationTaskCreator descriptorGenerationTaskCreator = new IvyPublicationDynamicDescriptorGenerationTaskCreator(project);
+        descriptorGenerationTaskCreator.monitor(extension.getPublications());
+
         // Create publish tasks automatically for any Ivy publication and repository combinations
-        Task publishLifecycleTask = project.getTasks().getByName(PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME);
-        IvyPublishDynamicTaskCreator publishTaskCreator = new IvyPublishDynamicTaskCreator(project.getTasks(), publishLifecycleTask);
+        Task publishLifecycleTask = tasks.getByName(PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME);
+        IvyPublishDynamicTaskCreator publishTaskCreator = new IvyPublishDynamicTaskCreator(tasks, publishLifecycleTask);
         publishTaskCreator.monitor(extension.getPublications(), extension.getRepositories());
     }
 
     private IvyPublication createPublication(String name, final Project project, Set<? extends Configuration> configurations) {
-        final DefaultIvyPublication publication = instantiator.newInstance(
+        return instantiator.newInstance(
                 DefaultIvyPublication.class,
                 name, instantiator, configurations, dependencyMetaDataProvider, fileResolver, project.getTasks()
         );
-
-        IvyModuleDescriptorInternal descriptor = publication.getDescriptor();
-        DslObject descriptorDslObject = new DslObject(descriptor);
-        ConventionMapping descriptorConventionMapping = descriptorDslObject.getConventionMapping();
-        descriptorConventionMapping.map("file", new Callable<Object>() {
-            public Object call() throws Exception {
-                return new File(project.getBuildDir(), "publications/" + publication.getName() + "/ivy.xml");
-            }
-        });
-
-        return publication;
     }
 }
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/GenerateIvyDescriptor.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/GenerateIvyDescriptor.java
new file mode 100644
index 0000000..4bb85db
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/GenerateIvyDescriptor.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.tasks;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.*;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
+import org.gradle.api.internal.artifacts.ivyservice.IvyModuleDescriptorWriter;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.tasks.DefaultTaskDependency;
+import org.gradle.api.internal.xml.XmlTransformer;
+import org.gradle.api.publish.ivy.IvyModuleDescriptor;
+import org.gradle.api.publish.ivy.internal.IvyModuleDescriptorInternal;
+import org.gradle.api.specs.Specs;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.TaskDependency;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.util.Date;
+
+/**
+ * Generates an Ivy XML Module Descriptor file.
+ *
+ * @since 1.4
+ */
+ at Incubating
+public class GenerateIvyDescriptor extends DefaultTask {
+
+    private IvyModuleDescriptor descriptor;
+    private final PublishArtifact descriptorArtifact;
+
+    private Action<? super XmlProvider> xmlAction;
+    private Object destination;
+
+    private final FileResolver fileResolver;
+    private final ArtifactPublicationServices publicationServices;
+
+    @Inject
+    public GenerateIvyDescriptor(FileResolver fileResolver, ArtifactPublicationServices publicationServices) {
+        this.fileResolver = fileResolver;
+        this.publicationServices = publicationServices;
+
+        // Never up to date; we don't understand the data structures.
+        getOutputs().upToDateWhen(Specs.satisfyNone());
+
+        this.descriptorArtifact = new IvyDescriptorArtifact();
+    }
+
+    /**
+     * The module descriptor metadata.
+     *
+     * @return The module descriptor.
+     */
+    public IvyModuleDescriptor getDescriptor() {
+        return descriptor;
+    }
+
+    public void setDescriptor(IvyModuleDescriptor descriptor) {
+        this.descriptor = descriptor;
+    }
+
+    /**
+     * The file the descriptor will be written to.
+     *
+     * @return The file the descriptor will be written to
+     */
+    @OutputFile
+    public File getDestination() {
+        return destination == null ? null : fileResolver.resolve(destination);
+    }
+
+    /**
+     * Sets the destination the descriptor will be written to.
+     *
+     * The value is resolved with {@link org.gradle.api.Project#file(Object)}
+     *
+     * @param destination The file the descriptor will be written to.
+     */
+    public void setDestination(Object destination) {
+        this.destination = destination;
+    }
+
+    public Action<? super XmlProvider> getXmlAction() {
+        return xmlAction;
+    }
+
+    public void setXmlAction(Action<? super XmlProvider> xmlAction) {
+        this.xmlAction = xmlAction;
+    }
+
+    public PublishArtifact getDescriptorArtifact() {
+        return descriptorArtifact;
+    }
+
+    @TaskAction
+    public void doGenerate() {
+        XmlTransformer xmlTransformer = new XmlTransformer();
+        Action<? super XmlProvider> xmlAction = getXmlAction();
+        if (xmlAction != null) {
+            xmlTransformer.addAction(xmlAction);
+        }
+
+        IvyModuleDescriptorInternal descriptorInternal = toIvyModuleDescriptorInternal(getDescriptor());
+
+        ModuleDescriptorConverter moduleDescriptorConverter = publicationServices.getDescriptorFileModuleConverter();
+        ModuleDescriptor moduleDescriptor = moduleDescriptorConverter.convert(descriptorInternal.getConfigurations(), descriptorInternal.getModule());
+        IvyModuleDescriptorWriter ivyModuleDescriptorWriter = publicationServices.getIvyModuleDescriptorWriter();
+        ivyModuleDescriptorWriter.write(moduleDescriptor, getDestination(), xmlTransformer);
+    }
+
+
+    private static IvyModuleDescriptorInternal toIvyModuleDescriptorInternal(IvyModuleDescriptor ivyModuleDescriptor) {
+        if (ivyModuleDescriptor == null) {
+            return null;
+        } else if (ivyModuleDescriptor instanceof IvyModuleDescriptorInternal) {
+            return (IvyModuleDescriptorInternal) ivyModuleDescriptor;
+        } else {
+            throw new InvalidUserDataException(
+                    String.format(
+                            "ivyModuleDescriptor implementations must implement the '%s' interface, implementation '%s' does not",
+                            IvyModuleDescriptorInternal.class.getName(),
+                            ivyModuleDescriptor.getClass().getName()
+                    )
+            );
+        }
+    }
+
+    private class IvyDescriptorArtifact implements PublishArtifact {
+        private final DefaultTaskDependency dependency;
+
+        public IvyDescriptorArtifact() {
+            this.dependency = new DefaultTaskDependency();
+            this.dependency.add(GenerateIvyDescriptor.this);
+        }
+
+        public String getName() {
+            return "ivy";
+        }
+
+        public String getExtension() {
+            return "xml";
+        }
+
+        public String getType() {
+            return "xml";
+        }
+
+        public String getClassifier() {
+            return null;
+        }
+
+        public File getFile() {
+            return getDestination();
+        }
+
+        public Date getDate() {
+            return null;
+        }
+
+        public TaskDependency getBuildDependencies() {
+            return dependency;
+        }
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/PublishToIvyRepository.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/PublishToIvyRepository.java
index 0a0bd92..58beabe 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/PublishToIvyRepository.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/PublishToIvyRepository.java
@@ -16,19 +16,21 @@
 
 package org.gradle.api.publish.ivy.tasks;
 
-import org.gradle.api.Buildable;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.Incubating;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
 import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.artifacts.repositories.IvyArtifactRepositoryInternal;
+import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
+import org.gradle.api.internal.artifacts.ArtifactPublisher;
+import org.gradle.api.publish.internal.PublishOperation;
 import org.gradle.api.publish.ivy.IvyPublication;
 import org.gradle.api.publish.ivy.internal.IvyNormalizedPublication;
 import org.gradle.api.publish.ivy.internal.IvyPublicationInternal;
 import org.gradle.api.publish.ivy.internal.IvyPublisher;
 import org.gradle.api.tasks.TaskAction;
 
+import javax.inject.Inject;
 import java.util.concurrent.Callable;
 
 /**
@@ -40,9 +42,14 @@ import java.util.concurrent.Callable;
 public class PublishToIvyRepository extends DefaultTask {
 
     private IvyPublicationInternal publication;
-    private IvyArtifactRepositoryInternal repository;
+    private IvyArtifactRepository repository;
+
+    private final ArtifactPublicationServices publicationServices;
+
+    @Inject
+    public PublishToIvyRepository(ArtifactPublicationServices publicationServices) {
+        this.publicationServices = publicationServices;
 
-    public PublishToIvyRepository() {
         // Allow the publication to participate in incremental build
         getInputs().files(new Callable<FileCollection>() {
             public FileCollection call() throws Exception {
@@ -51,15 +58,6 @@ public class PublishToIvyRepository extends DefaultTask {
             }
         });
 
-        // Allow the publication to have its dependencies fulfilled
-        // There may be dependencies that aren't about creating files and not covered above
-        dependsOn(new Callable<Buildable>() {
-            public Buildable call() throws Exception {
-                IvyPublicationInternal publicationInternal = getPublicationInternal();
-                return publicationInternal == null ? null : publicationInternal;
-            }
-        });
-
         // Should repositories be able to participate in incremental?
         // At the least, they may be able to express themselves as output files
         // They *might* have input files and other dependencies as well though
@@ -120,27 +118,7 @@ public class PublishToIvyRepository extends DefaultTask {
      * @param repository The repository to publish to
      */
     public void setRepository(IvyArtifactRepository repository) {
-        this.repository = toRepositoryInternal(repository);
-    }
-
-    private IvyArtifactRepositoryInternal getRepositoryInternal() {
-        return toRepositoryInternal(getRepository());
-    }
-
-    private static IvyArtifactRepositoryInternal toRepositoryInternal(IvyArtifactRepository repository) {
-        if (repository == null) {
-            return null;
-        } else if (repository instanceof IvyArtifactRepositoryInternal) {
-            return (IvyArtifactRepositoryInternal) repository;
-        } else {
-            throw new InvalidUserDataException(
-                    String.format(
-                            "repository objects must implement the '%s' interface, implementation '%s' does not",
-                            IvyArtifactRepositoryInternal.class.getName(),
-                            repository.getClass().getName()
-                    )
-            );
-        }
+        this.repository = repository;
     }
 
     @TaskAction
@@ -150,18 +128,24 @@ public class PublishToIvyRepository extends DefaultTask {
             throw new InvalidUserDataException("The 'publication' property is required");
         }
 
-        IvyArtifactRepositoryInternal repositoryInternal = getRepositoryInternal();
-        if (repositoryInternal == null) {
+        IvyArtifactRepository repository = getRepository();
+        if (repository == null) {
             throw new InvalidUserDataException("The 'repository' property is required");
         }
 
-        doPublish(publicationInternal, repositoryInternal);
+        doPublish(publicationInternal, repository);
     }
 
-    private void doPublish(IvyPublicationInternal publication, IvyArtifactRepositoryInternal repository) {
-        IvyPublisher publisher = repository.createPublisher();
-        IvyNormalizedPublication normalizedPublication = publication.asNormalisedPublication();
-        publisher.publish(normalizedPublication);
+    private void doPublish(final IvyPublicationInternal publication, final IvyArtifactRepository repository) {
+        new PublishOperation(publication, repository) {
+            @Override
+            protected void publish() throws Exception {
+                ArtifactPublisher artifactPublisher = publicationServices.createArtifactPublisher();
+                IvyNormalizedPublication normalizedPublication = publication.asNormalisedPublication();
+                IvyPublisher publisher = new IvyPublisher(artifactPublisher);
+                publisher.publish(normalizedPublication, repository);
+            }
+        }.run();
     }
 
 }
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/internal/IvyPublicationDynamicDescriptorGenerationTaskCreator.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/internal/IvyPublicationDynamicDescriptorGenerationTaskCreator.java
new file mode 100644
index 0000000..9ac3469
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/internal/IvyPublicationDynamicDescriptorGenerationTaskCreator.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.tasks.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.Project;
+import org.gradle.api.internal.ConventionMapping;
+import org.gradle.api.internal.plugins.DslObject;
+import org.gradle.api.publish.PublicationContainer;
+import org.gradle.api.publish.ivy.internal.IvyPublicationInternal;
+import org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor;
+
+import java.io.File;
+import java.util.concurrent.Callable;
+
+import static org.apache.commons.lang.StringUtils.capitalize;
+
+public class IvyPublicationDynamicDescriptorGenerationTaskCreator {
+
+    private final Project project;
+
+    public IvyPublicationDynamicDescriptorGenerationTaskCreator(Project project) {
+        this.project = project;
+    }
+
+    public void monitor(PublicationContainer publications) {
+        publications.withType(IvyPublicationInternal.class).all(new Action<IvyPublicationInternal>() {
+            public void execute(IvyPublicationInternal publication) {
+                create(publication);
+            }
+        });
+    }
+
+    private void create(final IvyPublicationInternal publication) {
+        String publicationName = publication.getName();
+
+        String descriptorTaskName = calculateDescriptorTaskName(publicationName);
+        GenerateIvyDescriptor descriptorTask = project.getTasks().add(descriptorTaskName, GenerateIvyDescriptor.class);
+        descriptorTask.setGroup("publishing");
+        descriptorTask.setDescription(String.format("Generates the Ivy Module Descriptor XML file for publication '%s'", publication.getName()));
+
+        ConventionMapping descriptorTaskConventionMapping = new DslObject(descriptorTask).getConventionMapping();
+        descriptorTaskConventionMapping.map("destination", new Callable<Object>() {
+            public Object call() throws Exception {
+                return new File(project.getBuildDir(), "publications/" + publication.getName() + "/ivy.xml");
+            }
+        });
+        descriptorTaskConventionMapping.map("descriptor", new Callable<Object>() {
+            public Object call() throws Exception {
+                return publication.getDescriptor();
+            }
+        });
+        descriptorTaskConventionMapping.map("xmlAction", new Callable<Object>() {
+            public Object call() throws Exception {
+                return publication.getDescriptor().getXmlAction();
+            }
+        });
+
+        publication.setDescriptorArtifact(descriptorTask.getDescriptorArtifact());
+    }
+
+    private String calculateDescriptorTaskName(String publicationName) {
+        return String.format(
+                "generate%sIvyModuleDescriptor",
+                publicationName.toLowerCase().equals("ivy") ? "" : capitalize(publicationName)
+        );
+    }
+
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/internal/IvyPublishDynamicTaskCreator.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/internal/IvyPublishDynamicTaskCreator.java
index e559b52..5d70cd3 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/internal/IvyPublishDynamicTaskCreator.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/internal/IvyPublishDynamicTaskCreator.java
@@ -17,11 +17,11 @@
 package org.gradle.api.publish.ivy.tasks.internal;
 
 import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectList;
+import org.gradle.api.NamedDomainObjectSet;
 import org.gradle.api.Task;
 import org.gradle.api.artifacts.ArtifactRepositoryContainer;
-import org.gradle.api.artifacts.repositories.ArtifactRepository;
-import org.gradle.api.internal.artifacts.repositories.IvyArtifactRepositoryInternal;
-import org.gradle.api.publish.Publication;
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
 import org.gradle.api.publish.PublicationContainer;
 import org.gradle.api.publish.ivy.internal.IvyPublicationInternal;
 import org.gradle.api.publish.ivy.tasks.PublishToIvyRepository;
@@ -43,17 +43,20 @@ public class IvyPublishDynamicTaskCreator {
     }
 
     public void monitor(final PublicationContainer publications, final ArtifactRepositoryContainer repositories) {
-        publications.all(new Action<Publication>() {
-            public void execute(Publication publication) {
-                for (ArtifactRepository repository : repositories) {
+        final NamedDomainObjectSet<IvyPublicationInternal> ivyPublications = publications.withType(IvyPublicationInternal.class);
+        final NamedDomainObjectList<IvyArtifactRepository> ivyRepositories = repositories.withType(IvyArtifactRepository.class);
+
+        ivyPublications.all(new Action<IvyPublicationInternal>() {
+            public void execute(IvyPublicationInternal publication) {
+                for (IvyArtifactRepository repository : ivyRepositories) {
                     maybeCreate(publication, repository);
                 }
             }
         });
 
-        repositories.whenObjectAdded(new Action<ArtifactRepository>() {
-            public void execute(ArtifactRepository repository) {
-                for (Publication publication : publications) {
+        ivyRepositories.all(new Action<IvyArtifactRepository>() {
+            public void execute(IvyArtifactRepository repository) {
+                for (IvyPublicationInternal publication : ivyPublications) {
                     maybeCreate(publication, repository);
                 }
             }
@@ -64,28 +67,20 @@ public class IvyPublishDynamicTaskCreator {
         //       (though this is a violation of the Named contract)
     }
 
-    private void maybeCreate(Publication publication, ArtifactRepository repository) {
-        if (!(publication instanceof IvyPublicationInternal)) {
-            return;
-        }
-        if (!(repository instanceof IvyArtifactRepositoryInternal)) {
-            return;
-        }
-
-        IvyPublicationInternal publicationInternal = (IvyPublicationInternal) publication;
-        IvyArtifactRepositoryInternal repositoryInternal = (IvyArtifactRepositoryInternal) repository;
-
+    private void maybeCreate(IvyPublicationInternal publication, IvyArtifactRepository repository) {
         String publicationName = publication.getName();
         String repositoryName = repository.getName();
-        String taskName = calculatePublishTaskName(publicationName, repositoryName);
 
-        PublishToIvyRepository task = tasks.add(taskName, PublishToIvyRepository.class);
-        task.setPublication(publicationInternal);
-        task.setRepository(repositoryInternal);
-        task.setGroup("publishing");
-        task.setDescription(String.format("Publishes Ivy publication '%s' to Ivy repository '%s'", publicationName, repositoryName));
+        String publishTaskName = calculatePublishTaskName(publicationName, repositoryName);
+        if (tasks.findByName(publishTaskName) == null) {
+            PublishToIvyRepository publishTask = tasks.add(publishTaskName, PublishToIvyRepository.class);
+            publishTask.setPublication(publication);
+            publishTask.setRepository(repository);
+            publishTask.setGroup("publishing");
+            publishTask.setDescription(String.format("Publishes Ivy publication '%s' to Ivy repository '%s'", publicationName, repositoryName));
 
-        publishLifecycleTask.dependsOn(task);
+            publishLifecycleTask.dependsOn(publishTask);
+        }
     }
 
     private String calculatePublishTaskName(String publicationName, String repositoryName) {
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/DefaultIvyPublicationTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/DefaultIvyPublicationTest.groovy
index a6ab5f8..cc193b4 100644
--- a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/DefaultIvyPublicationTest.groovy
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/DefaultIvyPublicationTest.groovy
@@ -15,11 +15,12 @@
  */
 
 package org.gradle.api.publish.ivy.internal
-
 import org.gradle.api.Task
 import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.PublishArtifact
 import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.tasks.TaskDependency
 import org.gradle.api.tasks.bundling.Jar
 import org.gradle.internal.reflect.Instantiator
 import org.gradle.util.HelperUtil
@@ -28,6 +29,7 @@ import spock.lang.Specification
 class DefaultIvyPublicationTest extends Specification {
 
     ProjectInternal project = HelperUtil.createRootProject()
+    def taskDependencies = Mock(TaskDependency)
 
     DefaultIvyPublication publication(String name = "ivy", Configuration... configurations) {
         Instantiator instantiator = project.getServices().get(Instantiator)
@@ -37,24 +39,29 @@ class DefaultIvyPublicationTest extends Specification {
     def "publishable files are the artifact files"() {
         when:
         def file1 = project.file("file1")
+        def descriptorFile1 = project.file("ivy1.xml")
+
         project.configurations { conf1 }
         project.artifacts { conf1 file1 }
         def p = publication(project.configurations.conf1)
+        p.descriptorArtifact = descriptorArtifact(descriptorFile1)
 
         then:
-        p.publishableFiles.singleFile == file1
+        p.publishableFiles.files == [file1, descriptorFile1] as Set
 
         when:
         def file2 = project.file("file2")
+        def descriptorFile2 = project.file("ivy2.xml")
         project.configurations { conf2 }
         project.artifacts { conf2 file2 }
         p = publication(project.configurations.conf1, project.configurations.conf2)
+        p.descriptorArtifact = descriptorArtifact(descriptorFile2)
 
         then:
-        p.publishableFiles.files == [file1, file2] as Set
+        p.publishableFiles.files == [file1, file2, descriptorFile2] as Set
     }
 
-    def "publication is built by what builds the artifacts"() {
+    def "publication is built by what builds the artifacts and descriptor"() {
         given:
         project.plugins.apply(JavaBasePlugin)
         Task dummyTask = project.task("dummyTask")
@@ -67,7 +74,7 @@ class DefaultIvyPublicationTest extends Specification {
         def p = publication(project.configurations.conf1)
 
         then:
-        p.buildDependencies.getDependencies(dummyTask) == [task1] as Set
+        p.publishableFiles.buildDependencies.getDependencies(dummyTask) == [task1] as Set
 
         when:
         def task2 = project.tasks.add("task2", Jar)
@@ -76,6 +83,32 @@ class DefaultIvyPublicationTest extends Specification {
         p = publication(project.configurations.conf1, project.configurations.conf2)
 
         then:
-        p.buildDependencies.getDependencies(dummyTask) == [task1, task2] as Set
+        p.publishableFiles.buildDependencies.getDependencies(dummyTask) == [task1, task2] as Set
+
+        when:
+        def task3 = project.tasks.add("task3")
+        p.descriptorArtifact = descriptorArtifact(project.file("ivy.xml"))
+        1 * taskDependencies.getDependencies(_) >> [task3]
+
+        then:
+        p.publishableFiles.buildDependencies.getDependencies(dummyTask) == [task1, task2, task3] as Set
+    }
+
+    def "can get publishable files when no descriptor file set"() {
+        when:
+        def file1 = project.file("file1")
+        project.configurations { conf1 }
+        project.artifacts { conf1 file1 }
+        def p = publication(project.configurations.conf1)
+
+        then:
+        p.publishableFiles.files == [file1] as Set
+    }
+
+    PublishArtifact descriptorArtifact(File descriptorFile) {
+        Mock(PublishArtifact) {
+            getFile() >> descriptorFile
+            getBuildDependencies() >> taskDependencies
+        }
     }
 }
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/plugins/IvyPublishPluginTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/plugins/IvyPublishPluginTest.groovy
index 7f3662d..7770c93 100644
--- a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/plugins/IvyPublishPluginTest.groovy
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/plugins/IvyPublishPluginTest.groovy
@@ -17,6 +17,7 @@
 package org.gradle.api.publish.ivy.plugins
 
 import org.gradle.api.Project
+import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.api.publish.PublishingExtension
 import org.gradle.api.publish.ivy.IvyPublication
 import org.gradle.api.publish.ivy.internal.IvyNormalizedPublication
@@ -80,6 +81,8 @@ class IvyPublishPluginTest extends Specification {
         }
 
         then:
-        publication.descriptor.transformer.transform("<things/>").contains('things foo="bar"')
+        def transformer = new XmlTransformer()
+        transformer.addAction(publication.descriptor.xmlAction)
+        transformer.transform("<things/>").contains('things foo="bar"')
     }
 }
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/PublishToIvyRepositoryTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/PublishToIvyRepositoryTest.groovy
index 70d0bc5..e6acdb8 100644
--- a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/PublishToIvyRepositoryTest.groovy
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/PublishToIvyRepositoryTest.groovy
@@ -15,18 +15,12 @@
  */
 
 package org.gradle.api.publish.ivy.tasks
-
 import org.gradle.api.InvalidUserDataException
 import org.gradle.api.Project
-import org.gradle.api.Task
 import org.gradle.api.artifacts.repositories.IvyArtifactRepository
-import org.gradle.api.internal.artifacts.repositories.IvyArtifactRepositoryInternal
 import org.gradle.api.publish.ivy.IvyPublication
 import org.gradle.api.publish.ivy.internal.IvyNormalizedPublication
 import org.gradle.api.publish.ivy.internal.IvyPublicationInternal
-import org.gradle.api.publish.ivy.internal.IvyPublisher
-import org.gradle.api.tasks.TaskDependency
-import org.gradle.api.tasks.TaskExecutionException
 import org.gradle.util.HelperUtil
 import spock.lang.Specification
 
@@ -41,11 +35,7 @@ class PublishToIvyRepositoryTest extends Specification {
         asNormalisedPublication() >> normalizedPublication
     }
 
-    def publisher = Mock(IvyPublisher) {}
-
-    def repository = Mock(IvyArtifactRepositoryInternal) {
-        createPublisher() >> publisher
-    }
+    def repository = Mock(IvyArtifactRepository) {}
 
     def setup() {
         project = HelperUtil.createRootProject()
@@ -66,74 +56,17 @@ class PublishToIvyRepositoryTest extends Specification {
         notThrown(Exception)
     }
 
-    def "repository must implement the internal interface"() {
-        when:
-        publish.repository = [:] as IvyArtifactRepository
-
-        then:
-        thrown(InvalidUserDataException)
-
-        when:
-        publish.repository = [:] as IvyArtifactRepositoryInternal
-
-        then:
-        notThrown(Exception)
-    }
-
-    def "the dependencies of the publication are dependencies of the task"() {
+    def "the publishableFiles of the publication are inputs of the task"() {
         given:
-        Task otherTask = project.task("other")
         def publishableFiles = project.files("a", "b", "c")
 
         publication.getPublishableFiles() >> publishableFiles
-        publication.getBuildDependencies() >> {
-            new TaskDependency() {
-                Set<? extends Task> getDependencies(Task task) {
-                    [otherTask] as Set
-                }
-            }
-        }
 
         when:
         publish.publication = publication
 
         then:
         publish.inputs.files.files == publishableFiles.files
-        publish.taskDependencies.getDependencies(publish) == [otherTask] as Set
-    }
-
-    def "repository and publication are required"() {
-        given:
-        repository = Mock(IvyArtifactRepositoryInternal) {
-            1 * createPublisher() >> publisher
-        }
-
-        when:
-        publish.execute()
-
-        then:
-        def e = thrown(TaskExecutionException)
-        e.cause instanceof InvalidUserDataException
-        e.cause.message == "The 'publication' property is required"
-
-        when:
-        publish = createPublish("publish2")
-        publish.publication = publication
-        publish.execute()
-
-        then:
-        e = thrown(TaskExecutionException)
-        e.cause instanceof InvalidUserDataException
-        e.cause.message == "The 'repository' property is required"
-
-        when:
-        publish = createPublish("publish3")
-        publish.publication = publication
-        publish.repository = repository
-        publish.execute()
-
-        then:
-        true
     }
 
     PublishToIvyRepository createPublish(String name) {
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/internal/IvyPublicationDynamicDescriptorGenerationTaskCreatorTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/internal/IvyPublicationDynamicDescriptorGenerationTaskCreatorTest.groovy
new file mode 100644
index 0000000..943a5ec
--- /dev/null
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/internal/IvyPublicationDynamicDescriptorGenerationTaskCreatorTest.groovy
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.tasks.internal
+import org.gradle.api.publish.Publication
+import org.gradle.api.publish.PublishingExtension
+import org.gradle.api.publish.ivy.IvyModuleDescriptor
+import org.gradle.api.publish.ivy.IvyPublication
+import org.gradle.api.publish.ivy.internal.IvyModuleDescriptorInternal
+import org.gradle.api.publish.ivy.internal.IvyPublicationInternal
+import org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor
+import org.gradle.api.publish.plugins.PublishingPlugin
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class IvyPublicationDynamicDescriptorGenerationTaskCreatorTest extends Specification {
+
+    def project = HelperUtil.createRootProject()
+    def creator = new IvyPublicationDynamicDescriptorGenerationTaskCreator(project)
+    PublishingExtension publishing
+
+    def setup() {
+        project.plugins.apply(PublishingPlugin)
+        publishing = project.extensions.getByType(PublishingExtension)
+        creator.monitor(publishing.publications)
+    }
+
+    def "creates tasks"() {
+        expect:
+        descriptorGeneratorTasks.size() == 0
+
+        when:
+        publishing.repositories.ivy { }
+        publishing.publications.add(publication("foo"))
+
+        then:
+        descriptorGeneratorTasks.size() == 0
+
+        when:
+        publishing.publications.add(ivyPublication("ivy"))
+
+        then:
+        descriptorGeneratorTasks.size() == 1
+        GenerateIvyDescriptor task = project.tasks.generateIvyModuleDescriptor
+        task.group == "publishing"
+        task.description != null
+
+        when:
+        publishing.publications.add(ivyPublication("other"))
+
+        then:
+        descriptorGeneratorTasks.size() == 2
+        def task2 = project.tasks.generateOtherIvyModuleDescriptor
+        task2.group == "publishing"
+    }
+
+    Publication publication(String name) {
+        Mock(Publication) {
+            getName() >> name
+        }
+    }
+
+    IvyPublication ivyPublication(String name) {
+        IvyModuleDescriptor moduleDescriptor = Mock(IvyModuleDescriptorInternal)
+        Mock(IvyPublicationInternal) {
+            getName() >> name
+            getDescriptor() >> moduleDescriptor
+            1 * setDescriptorArtifact({it.file.path.contains name})
+        }
+    }
+
+    def getDescriptorGeneratorTasks() {
+        project.tasks.withType(GenerateIvyDescriptor)
+    }
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePlugin.groovy b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePlugin.groovy
index 3a3865a..8477618 100644
--- a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePlugin.groovy
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePlugin.groovy
@@ -18,21 +18,14 @@ package org.gradle.plugins.javascript.base
 
 import org.gradle.api.Plugin
 import org.gradle.api.Project
-import org.gradle.api.internal.artifacts.DependencyResolutionServices
 import org.gradle.api.plugins.BasePlugin
 
-import javax.inject.Inject
-
 class JavaScriptBasePlugin implements Plugin<Project> {
-    private final DependencyResolutionServices dependencyResolutionServices
 
-    @Inject
-    JavaScriptBasePlugin(DependencyResolutionServices dependencyResolutionServices) {
-        this.dependencyResolutionServices = dependencyResolutionServices
-    }
 
     void apply(Project project) {
         project.apply(plugin: BasePlugin)
-        project.extensions.create(JavaScriptExtension.NAME, JavaScriptExtension, dependencyResolutionServices.baseRepositoryFactory)
+        project.extensions.create(JavaScriptExtension.NAME, JavaScriptExtension)
+        project.repositories.extensions.create(JavaScriptRepositoriesExtension.NAME, JavaScriptRepositoriesExtension, project.repositories)
     }
 }
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptExtension.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptExtension.java
index 6699de3..29b4498 100644
--- a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptExtension.java
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptExtension.java
@@ -16,45 +16,8 @@
 
 package org.gradle.plugins.javascript.base;
 
-import groovy.lang.Closure;
-import org.gradle.api.artifacts.repositories.ArtifactRepository;
-import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
-import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.api.internal.artifacts.BaseRepositoryFactory;
-import org.gradle.api.internal.artifacts.repositories.layout.PatternRepositoryLayout;
-
 public class JavaScriptExtension {
 
     public static final String NAME = "javaScript";
 
-    public static final String GRADLE_PUBLIC_JAVASCRIPT_REPO_URL = "http://repo.gradle.org/gradle/javascript-public";
-    public static final String GOOGLE_APIS_REPO_URL = "http://ajax.googleapis.com/ajax/libs";
-
-    private final BaseRepositoryFactory baseRepositoryFactory;
-
-    public JavaScriptExtension(BaseRepositoryFactory baseRepositoryFactory) {
-        this.baseRepositoryFactory = baseRepositoryFactory;
-    }
-
-    public ArtifactRepository getGradlePublicJavaScriptRepository() {
-        MavenArtifactRepository repo = baseRepositoryFactory.createMavenRepository();
-        repo.setUrl(GRADLE_PUBLIC_JAVASCRIPT_REPO_URL);
-        repo.setName("Gradle Public JavaScript Repository");
-        return repo;
-    }
-
-    public ArtifactRepository getGoogleApisRepository() {
-        IvyArtifactRepository repo = baseRepositoryFactory.createIvyRepository();
-        repo.setName("Google Libraries Repository");
-        repo.setUrl(GOOGLE_APIS_REPO_URL);
-        repo.layout("pattern", new Closure(this) {
-            public void doCall() {
-                PatternRepositoryLayout layout = (PatternRepositoryLayout) getDelegate();
-                layout.artifact("[organization]/[revision]/[module].[ext]");
-                layout.ivy("[organization]/[revision]/[module].xml");
-            }
-        });
-        return repo;
-    }
-
 }
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptRepositoriesExtension.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptRepositoriesExtension.java
new file mode 100644
index 0000000..011096c
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptRepositoriesExtension.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.javascript.base;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.internal.Actions;
+import org.gradle.api.internal.artifacts.repositories.layout.PatternRepositoryLayout;
+
+public class JavaScriptRepositoriesExtension {
+
+    public static final String NAME = "javaScript";
+
+    public static final String GRADLE_PUBLIC_JAVASCRIPT_REPO_URL = "http://repo.gradle.org/gradle/javascript-public";
+    public static final String GOOGLE_APIS_REPO_URL = "http://ajax.googleapis.com/ajax/libs";
+
+    private final RepositoryHandler repositories;
+
+    public JavaScriptRepositoriesExtension(RepositoryHandler repositories) {
+        this.repositories = repositories;
+    }
+
+    public ArtifactRepository gradle() {
+        return gradle(Actions.doNothing());
+    }
+
+    public MavenArtifactRepository gradle(final Action<? super MavenArtifactRepository> action) {
+        return repositories.maven(new Action<MavenArtifactRepository>() {
+            public void execute(MavenArtifactRepository repository) {
+                repository.setName("gradleJs");
+                repository.setUrl(GRADLE_PUBLIC_JAVASCRIPT_REPO_URL);
+                action.execute(repository);
+            }
+        });
+    }
+
+    public IvyArtifactRepository googleApis() {
+        return googleApis(Actions.doNothing());
+    }
+
+    public IvyArtifactRepository googleApis(final Action<? super IvyArtifactRepository> action) {
+        return repositories.ivy(new Action<IvyArtifactRepository>() {
+            public void execute(IvyArtifactRepository repo) {
+                repo.setName("googleApisJs");
+                repo.setUrl(GOOGLE_APIS_REPO_URL);
+                repo.layout("pattern", new Closure(this) {
+                    public void doCall() {
+                        PatternRepositoryLayout layout = (PatternRepositoryLayout) getDelegate();
+                        layout.artifact("[organization]/[revision]/[module].[ext]");
+                        layout.ivy("[organization]/[revision]/[module].xml");
+                    }
+                });
+                action.execute(repo);
+            }
+        });
+    }
+
+}
diff --git a/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginTest.groovy b/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginTest.groovy
index b86f718..d7fdcd4 100644
--- a/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginTest.groovy
+++ b/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginTest.groovy
@@ -17,9 +17,9 @@
 package org.gradle.plugins.javascript.base
 
 import org.gradle.api.Project
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository
 import org.gradle.testfixtures.ProjectBuilder
 import spock.lang.Specification
-import org.gradle.api.artifacts.repositories.MavenArtifactRepository
 
 class JavaScriptBasePluginTest extends Specification {
     Project project = ProjectBuilder.builder().build()
@@ -37,9 +37,10 @@ class JavaScriptBasePluginTest extends Specification {
         project.apply(plugin: JavaScriptBasePlugin)
 
         then:
-        project.javaScript.gradlePublicJavaScriptRepository instanceof MavenArtifactRepository
-        MavenArtifactRepository repo = project.javaScript.gradlePublicJavaScriptRepository as MavenArtifactRepository
-        repo.url.toString() == JavaScriptExtension.GRADLE_PUBLIC_JAVASCRIPT_REPO_URL
+        project.repositories.javaScript.gradle()
+        project.repositories.gradleJs instanceof MavenArtifactRepository
+        MavenArtifactRepository repo = project.repositories.gradleJs as MavenArtifactRepository
+        repo.url.toString() == JavaScriptRepositoriesExtension.GRADLE_PUBLIC_JAVASCRIPT_REPO_URL
     }
 
 }
diff --git a/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServerFactoryTest.groovy b/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServerFactoryTest.groovy
index 70712d2..6615d34 100644
--- a/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServerFactoryTest.groovy
+++ b/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServerFactoryTest.groovy
@@ -16,15 +16,16 @@
 
 package org.gradle.plugins.javascript.envjs.http.simple
 
-import spock.lang.Specification
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
-import org.gradle.util.TemporaryFolder
+import spock.lang.Specification
+
 import java.nio.charset.Charset
-import org.gradle.util.TestFile
 
 class SimpleHttpFileServerFactoryTest extends Specification {
 
-    @Rule TemporaryFolder tmp = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider tmp = new TestNameTestDirectoryProvider()
     TestFile root
 
     def setup() {
diff --git a/subprojects/javascript/src/testFixtures/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginTestFixtures.groovy b/subprojects/javascript/src/testFixtures/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginTestFixtures.groovy
index bee2e40..c0e115b 100644
--- a/subprojects/javascript/src/testFixtures/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginTestFixtures.groovy
+++ b/subprojects/javascript/src/testFixtures/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginTestFixtures.groovy
@@ -21,7 +21,7 @@ class JavaScriptBasePluginTestFixtures {
     static String getGradlePublicJSRepoScript() {
         """
         repositories {
-            add ${JavaScriptExtension.NAME}.gradlePublicJavaScriptRepository
+            javaScript.gradle()
         }
         """
     }
@@ -29,7 +29,7 @@ class JavaScriptBasePluginTestFixtures {
     static String getGoogleRepoScript() {
         """
         repositories {
-            add ${JavaScriptExtension.NAME}.googleApisRepository
+            javaScript.googleApis()
         }
         """
     }
diff --git a/subprojects/launcher/launcher.gradle b/subprojects/launcher/launcher.gradle
index 4c9ae16..acdde9a 100644
--- a/subprojects/launcher/launcher.gradle
+++ b/subprojects/launcher/launcher.gradle
@@ -15,8 +15,6 @@ dependencies {
     compile libraries.slf4j_api
 
     startScriptGenerator project(':plugins')
-
-    testRuntime project(':diagnostics')
 }
 
 useTestFixtures()
@@ -60,9 +58,7 @@ class StartScriptGenerator extends DefaultTask {
     @TaskAction
     def generate() {
         logging.captureStandardOutput(LogLevel.INFO)
-        def factory = services.get(ClassLoaderFactory)
-        def classLoader = factory.createIsolatedClassLoader(classpath.collect { it.toURI() })
-        def generator = classLoader.loadClass('org.gradle.api.internal.plugins.StartScriptGenerator').newInstance()
+        def generator = new org.gradle.api.internal.plugins.StartScriptGenerator()
         generator.applicationName = 'Gradle'
         generator.optsEnvironmentVar = 'GRADLE_OPTS'
         generator.exitEnvironmentVar = 'GRADLE_EXIT_CONSOLE'
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/GradleConfigurabilityIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/GradleConfigurabilityIntegrationSpec.groovy
index c996cb4..d5d3470 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/GradleConfigurabilityIntegrationSpec.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/GradleConfigurabilityIntegrationSpec.groovy
@@ -18,7 +18,7 @@ package org.gradle.launcher
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.AvailableJavaHomes
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import org.gradle.internal.jvm.Jvm
 import org.gradle.internal.nativeplatform.filesystem.FileSystems
 import org.gradle.util.Requires
@@ -29,21 +29,21 @@ import spock.lang.IgnoreIf
 /**
  * by Szczepan Faber, created at: 1/20/12
  */
- at IgnoreIf( { !GradleDistributionExecuter.getSystemPropertyExecuter().forks })
+ at IgnoreIf( { GradleContextualExecuter.embedded })
 class GradleConfigurabilityIntegrationSpec extends AbstractIntegrationSpec {
 
     def setup() {
-        distribution.requireIsolatedDaemons()
+        executer.requireIsolatedDaemons()
     }
 
     def buildSucceeds(String script) {
-        distribution.file('build.gradle') << script
+        file('build.gradle') << script
         executer.withArguments("--info").withNoDefaultJvmArgs().run()
     }
 
     def "honours jvm args specified in gradle.properties"() {
         given:
-        distribution.file("gradle.properties") << "org.gradle.jvmargs=-Dsome-prop=some-value -Xmx16m"
+        file("gradle.properties") << "org.gradle.jvmargs=-Dsome-prop=some-value -Xmx16m"
 
         expect:
         buildSucceeds """
@@ -56,12 +56,12 @@ assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.conta
     def "connects to the daemon if java home is a symlink"() {
         given:
         def javaHome = Jvm.current().javaHome
-        def javaLink = distribution.testFile("javaLink")
+        def javaLink = file("javaLink")
         FileSystems.default.createSymbolicLink(javaLink, javaHome)
-        distribution.getTestDir().file("tmp").deleteDir().createDir()
+        file("tmp").deleteDir().createDir()
 
         String linkPath = TextUtil.escapeString(javaLink.absolutePath)
-        distribution.file("gradle.properties") << "org.gradle.java.home=$linkPath"
+        file("gradle.properties") << "org.gradle.java.home=$linkPath"
 
         when:
         buildSucceeds "println 'java home =' + System.getProperty('java.home')"
@@ -77,7 +77,7 @@ assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.conta
     //TODO SF add coverage for reconnecting to those daemons.
     def "honours jvm sys property that contain a space in gradle.properties"() {
         given:
-        distribution.file("gradle.properties") << 'org.gradle.jvmargs=-Dsome-prop="i have space"'
+        file("gradle.properties") << 'org.gradle.jvmargs=-Dsome-prop="i have space"'
 
         expect:
         buildSucceeds """
@@ -87,7 +87,7 @@ assert System.getProperty('some-prop').toString() == 'i have space'
 
     def "honours jvm option that contain a space in gradle.properties"() {
         given:
-        distribution.file("gradle.properties") << 'org.gradle.jvmargs=-XX:HeapDumpPath="/tmp/with space" -Dsome-prop="and some more stress..."'
+        file("gradle.properties") << 'org.gradle.jvmargs=-XX:HeapDumpPath="/tmp/with space" -Dsome-prop="and some more stress..."'
 
         expect:
         buildSucceeds """
@@ -101,7 +101,7 @@ assert inputArgs.find { it.contains('-XX:HeapDumpPath=') }
         given:
         File javaHome = AvailableJavaHomes.bestAlternative
         String javaPath = TextUtil.escapeString(javaHome.canonicalPath)
-        distribution.file("gradle.properties") << "org.gradle.java.home=$javaPath"
+        file("gradle.properties") << "org.gradle.java.home=$javaPath"
 
         expect:
         buildSucceeds "assert System.getProperty('java.home').startsWith('$javaPath')"
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/SystemClassLoaderTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/SystemClassLoaderTest.groovy
index 8b6280f..5bfdf34 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/SystemClassLoaderTest.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/SystemClassLoaderTest.groovy
@@ -16,8 +16,8 @@
 package org.gradle.launcher
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import spock.lang.IgnoreIf
-import static org.gradle.integtests.fixtures.GradleDistributionExecuter.getSystemPropertyExecuter
 
 /**
  * Verifies that Gradle doesn't pollute the system class loader.
@@ -39,7 +39,7 @@ class SystemClassLoaderTest extends AbstractIntegrationSpec {
         for a class it doesn't have, it simply returns null. I've not been able to find any official documentation
         explaining why this is.
     */
-    @IgnoreIf({ !getSystemPropertyExecuter().forks })
+    @IgnoreIf({ GradleContextualExecuter.embedded })
     def "daemon bootstrap classpath is bare bones"() {
         given:
         buildFile << """
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonFeedbackIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonFeedbackIntegrationSpec.groovy
index fbcd828..6215b05 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonFeedbackIntegrationSpec.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonFeedbackIntegrationSpec.groovy
@@ -20,19 +20,20 @@ import org.gradle.launcher.daemon.client.DaemonDisappearedException
 import org.gradle.launcher.daemon.logging.DaemonMessages
 import org.gradle.util.TextUtil
 import spock.lang.Timeout
-import static org.gradle.tests.fixtures.ConcurrentTestUtil.poll
+
+import static org.gradle.test.fixtures.ConcurrentTestUtil.poll
 
 /**
  * by Szczepan Faber, created at: 1/20/12
  */
 class DaemonFeedbackIntegrationSpec extends DaemonIntegrationSpec {
     def setup() {
-        distribution.requireIsolatedDaemons()
+        executer.requireIsolatedDaemons()
     }
 
     def "daemon keeps logging to the file even if the build is started"() {
         given:
-        distribution.file("build.gradle") << """
+        file("build.gradle") << """
 task sleep << {
     println 'taking a nap...'
     Thread.sleep(10000)
@@ -45,7 +46,7 @@ task sleep << {
 
         then:
         poll(60) {
-            assert readLog(distribution.daemonBaseDir).contains("taking a nap...")
+            assert readLog(executer.daemonBaseDir).contains("taking a nap...")
         }
 
         when:
@@ -54,7 +55,7 @@ task sleep << {
         then:
         sleeper.waitForFailure()
 
-        def log = readLog(distribution.daemonBaseDir)
+        def log = readLog(executer.daemonBaseDir)
         assert log.contains(DaemonMessages.REMOVING_PRESENCE_DUE_TO_STOP)
         assert log.contains(DaemonMessages.DAEMON_VM_SHUTTING_DOWN)
     }
@@ -72,7 +73,7 @@ task sleep << {
 
     @Timeout(25)
     def "promptly shows decent message when awkward java home used"() {
-        def dummyJdk = distribution.file("dummyJdk").createDir()
+        def dummyJdk = file("dummyJdk").createDir()
         assert dummyJdk.isDirectory()
         def jdkPath = TextUtil.escapeString(dummyJdk.canonicalPath)
         
@@ -87,13 +88,13 @@ task sleep << {
 
     def "daemon log contains all necessary logging"() {
         given:
-        distribution.file("build.gradle") << "println 'Hello build!'"
+        file("build.gradle") << "println 'Hello build!'"
 
         when:
         executer.withArguments("-i").run()
 
         then:
-        def log = readLog(distribution.daemonBaseDir)
+        def log = readLog(executer.daemonBaseDir)
 
         //output before started relying logs via connection
         log.count(DaemonMessages.PROCESS_STARTED) == 1
@@ -106,7 +107,7 @@ task sleep << {
         executer.withArguments("-i").run()
 
         then:
-        def aLog = readLog(distribution.daemonBaseDir)
+        def aLog = readLog(executer.daemonBaseDir)
 
         aLog.count(DaemonMessages.PROCESS_STARTED) == 1
         aLog.count(DaemonMessages.STARTED_RELAYING_LOGS) == 2
@@ -115,19 +116,19 @@ task sleep << {
 
     def "background daemon infrastructure logs with DEBUG"() {
         given:
-        distribution.file("build.gradle") << "task foo << { println 'hey!' }"
+        file("build.gradle") << "task foo << { println 'hey!' }"
 
         when: "runing build with --info"
         executer.withArguments("-i").withTasks('foo').run()
 
         then:
-        def log = readLog(distribution.daemonBaseDir)
+        def log = readLog(executer.daemonBaseDir)
         log.findAll(DaemonMessages.STARTED_EXECUTING_COMMAND).size() == 1
 
         poll(60) {
             //in theory the client could have received result and complete
             // but the daemon has not yet finished processing hence polling
-            def daemonLog = readLog(distribution.daemonBaseDir)
+            def daemonLog = readLog(executer.daemonBaseDir)
             daemonLog.findAll(DaemonMessages.FINISHED_EXECUTING_COMMAND).size() == 1
             daemonLog.findAll(DaemonMessages.FINISHED_BUILD).size() == 1
         }
@@ -136,13 +137,13 @@ task sleep << {
         executer.withArguments("-i").withTasks('foo').run()
 
         then:
-        def aLog = readLog(distribution.daemonBaseDir)
+        def aLog = readLog(executer.daemonBaseDir)
         aLog.findAll(DaemonMessages.STARTED_EXECUTING_COMMAND).size() == 2
     }
 
     def "daemon log honors log levels for logging"() {
         given:
-        distribution.file("build.gradle") << """
+        file("build.gradle") << """
             println 'println me!'
 
             logger.debug('debug me!')
@@ -157,7 +158,7 @@ task sleep << {
         executer.withArguments("-q").run()
 
         then:
-        def log = readLog(distribution.daemonBaseDir)
+        def log = readLog(executer.daemonBaseDir)
 
         //daemon logs to file eagerly regardless of the build log level
         log.count(DaemonMessages.STARTED_RELAYING_LOGS) == 1
@@ -173,7 +174,7 @@ task sleep << {
 
     def "disappearing daemon makes client log useful information"() {
         given:
-        distribution.file("build.gradle") << "System.exit(0)"
+        file("build.gradle") << "System.exit(0)"
 
         when:
         def failure = executer.withArguments("-q").runWithFailure()
@@ -185,7 +186,7 @@ task sleep << {
 
     def "foreground daemon log honors log levels for logging"() {
         given:
-        distribution.file("build.gradle") << """
+        file("build.gradle") << """
             logger.debug('debug me!')
             logger.info('info me!')
         """
@@ -200,7 +201,7 @@ task sleep << {
         def infoBuild = executer.withArguments("-i", "-Dorg.gradle.jvmargs=-ea").run()
 
         then:
-        getLogs(distribution.daemonBaseDir).size() == 0 //we should connect to the foreground daemon so no log was created
+        getLogs(executer.daemonBaseDir).size() == 0 //we should connect to the foreground daemon so no log was created
 
         daemon.standardOutput.count(DaemonMessages.ABOUT_TO_START_RELAYING_LOGS) == 0
         daemon.standardOutput.count("info me!") == 1
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonInitScriptHandlingIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonInitScriptHandlingIntegrationTest.groovy
new file mode 100644
index 0000000..d2507f7
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonInitScriptHandlingIntegrationTest.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.launcher.daemon
+
+import org.gradle.integtests.fixtures.executer.DaemonGradleExecuter
+import org.gradle.integtests.fixtures.executer.DefaultGradleDistribution
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.test.fixtures.file.TestFile
+import spock.lang.Issue
+
+/**
+ * Tests that init scripts are used from the _clients_ GRADLE_HOME, not the daemon server's.
+ */
+ at Issue("http://issues.gradle.org/browse/GRADLE-2408")
+class DaemonInitScriptHandlingIntegrationTest extends DaemonIntegrationSpec {
+
+    TestFile createDistribution(int i) {
+        def distro = file("distro$i")
+        distro.copyFrom(distribution.getGradleHomeDir())
+        distro.file("bin", OperatingSystem.current().getScriptName("gradle")).permissions = 'rwx------'
+        distro.file("init.d/init.gradle") << """
+            gradle.allprojects {
+                task echo << { println "from distro $i" }
+            }
+        """
+        distro
+    }
+
+    def runWithGradleHome(TestFile gradleHome) {
+        def copiedDistro = new DefaultGradleDistribution(executer.distribution.version, gradleHome, null)
+        executer.copyTo(new DaemonGradleExecuter(copiedDistro, executer.testDirectoryProvider)).run()
+    }
+
+    def "init scripts from client distribution are used, not from the test"() {
+        given:
+        def distro1 = createDistribution(1)
+        def distro2 = createDistribution(2)
+
+        and:
+        buildFile << """
+            echo.doLast {
+                println "runtime gradle home: \${gradle.gradleHomeDir}"
+            }
+        """
+
+        and:
+        executer.withTasks("echo")
+
+        when:
+        def distro1Result = runWithGradleHome(distro1)
+
+        then:
+        distro1Result.output.contains "from distro 1"
+        distro1Result.output.contains "runtime gradle home: ${distro1.absolutePath}"
+
+        when:
+        def distro2Result = runWithGradleHome(distro2)
+
+        then:
+        distro2Result.output.contains "from distro 2"
+        distro1Result.output.contains "runtime gradle home: ${distro1.absolutePath}"
+    }
+
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonInitialCommunicationFailureIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonInitialCommunicationFailureIntegrationSpec.groovy
index f017955..82249cc 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonInitialCommunicationFailureIntegrationSpec.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonInitialCommunicationFailureIntegrationSpec.groovy
@@ -24,7 +24,7 @@ import org.junit.Rule
 import spock.lang.IgnoreIf
 import spock.lang.Issue
 
-import static org.gradle.tests.fixtures.ConcurrentTestUtil.poll
+import static org.gradle.test.fixtures.ConcurrentTestUtil.poll
 
 /**
  * by Szczepan Faber, created at: 1/20/12
@@ -45,7 +45,7 @@ class DaemonInitialCommunicationFailureIntegrationSpec extends DaemonIntegration
 
         then:
         //there should be one idle daemon
-        def daemon = new DaemonLogsAnalyzer(distribution.daemonBaseDir).daemon
+        def daemon = new DaemonLogsAnalyzer(executer.daemonBaseDir).daemon
 
         when:
         // Wait until the daemon has finished updating the registry. Killing it halfway through the registry update will leave the registry corrupted,
@@ -81,7 +81,7 @@ class DaemonInitialCommunicationFailureIntegrationSpec extends DaemonIntegration
         buildSucceeds()
 
         then:
-        def daemon = new DaemonLogsAnalyzer(distribution.daemonBaseDir).daemon
+        def daemon = new DaemonLogsAnalyzer(executer.daemonBaseDir).daemon
 
         when:
         daemon.waitUntilIdle()
@@ -111,7 +111,7 @@ class DaemonInitialCommunicationFailureIntegrationSpec extends DaemonIntegration
         buildSucceeds()
 
         then:
-        def daemon = new DaemonLogsAnalyzer(distribution.daemonBaseDir).daemon
+        def daemon = new DaemonLogsAnalyzer(executer.daemonBaseDir).daemon
 
         when:
         daemon.waitUntilIdle()
@@ -121,7 +121,7 @@ class DaemonInitialCommunicationFailureIntegrationSpec extends DaemonIntegration
         buildSucceeds()
 
         and:
-        def analyzer = new DaemonLogsAnalyzer(distribution.daemonBaseDir)
+        def analyzer = new DaemonLogsAnalyzer(executer.daemonBaseDir)
         analyzer.daemons.size() == 2        //2 daemon participated
         analyzer.registry.all.size() == 1   //only one address in the registry
     }
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonIntegrationSpec.groovy
index a23b5d5..b9935db 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonIntegrationSpec.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonIntegrationSpec.groovy
@@ -17,25 +17,25 @@
 package org.gradle.launcher.daemon
 
 import ch.qos.logback.classic.Level
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.junit.Rule
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.executer.DaemonGradleExecuter
 import org.slf4j.LoggerFactory
-import spock.lang.Specification
-
-import static org.gradle.integtests.fixtures.GradleDistributionExecuter.Executer.daemon
 
 /**
  * by Szczepan Faber, created at: 2/1/12
  */
-class DaemonIntegrationSpec extends Specification {
+class DaemonIntegrationSpec extends AbstractIntegrationSpec {
 
-    @Rule public final GradleDistribution distribution = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter(daemon)
     String output
 
+    @Override
+    DaemonGradleExecuter getExecuter() {
+        super.executer as DaemonGradleExecuter
+    }
+
     def setup() {
-        distribution.requireIsolatedDaemons()
+        executer = new DaemonGradleExecuter(distribution, temporaryFolder)
+        executer.requireIsolatedDaemons()
         LoggerFactory.getLogger("org.gradle.cache.internal.DefaultFileLockManager").level = Level.INFO
     }
 
@@ -45,8 +45,12 @@ class DaemonIntegrationSpec extends Specification {
     }
 
     void buildSucceeds(String script = '') {
-        distribution.file('build.gradle') << script
+        file('build.gradle') << script
         def result = executer.withArguments("--info").withNoDefaultJvmArgs().run()
         output = result.output
     }
+
+    def cleanup() {
+        stopDaemonsNow()
+    }
 }
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonLifecycleSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonLifecycleSpec.groovy
index 7a67885..00e2445 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonLifecycleSpec.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonLifecycleSpec.groovy
@@ -17,7 +17,7 @@
 package org.gradle.launcher.daemon
 
 import org.gradle.integtests.fixtures.AvailableJavaHomes
-import org.gradle.integtests.fixtures.GradleHandle
+import org.gradle.integtests.fixtures.executer.GradleHandle
 import org.gradle.internal.jvm.Jvm
 import org.gradle.internal.os.OperatingSystem
 import org.gradle.launcher.daemon.client.DaemonDisappearedException
@@ -25,7 +25,7 @@ import org.gradle.launcher.daemon.testing.DaemonContextParser
 import org.gradle.launcher.daemon.testing.DaemonEventSequenceBuilder
 import spock.lang.IgnoreIf
 
-import static org.gradle.tests.fixtures.ConcurrentTestUtil.poll
+import static org.gradle.test.fixtures.ConcurrentTestUtil.poll
 
 /**
  * Outlines the lifecycle of the daemon given different sequences of events.
@@ -55,7 +55,7 @@ class DaemonLifecycleSpec extends DaemonIntegrationSpec {
         new DaemonEventSequenceBuilder(stateTransitionTimeout * 1000)
 
     def buildDir(buildNum) {
-        distribution.file("builds/$buildNum")
+        file("builds/$buildNum")
     }
 
     def buildDirWithScript(buildNum, buildScript) {
@@ -148,6 +148,7 @@ class DaemonLifecycleSpec extends DaemonIntegrationSpec {
         }
         executer.withArguments("--foreground", "--info", "-Dorg.gradle.daemon.idletimeout=${daemonIdleTimeout * 1000}")
         foregroundDaemons << executer.start()
+        executer.withJavaHome(null)
     }
 
     //this is a windows-safe way of killing the process
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonSystemPropertiesIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonSystemPropertiesIntegrationTest.groovy
index b5c4ade..00b642b 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonSystemPropertiesIntegrationTest.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonSystemPropertiesIntegrationTest.groovy
@@ -22,7 +22,7 @@ import spock.lang.Issue
 class DaemonSystemPropertiesIntegrationTest extends DaemonIntegrationSpec {
     def "standard and sun. client JVM system properties are not carried over to daemon JVM"() {
         given:
-        distribution.file("build.gradle") << """
+        file("build.gradle") << """
 task verify << {
     assert System.getProperty("java.vendor") != "hollywood"
     assert System.getProperty("java.vendor") != null
@@ -31,19 +31,19 @@ task verify << {
         """
 
         expect:
-        executer.withEnvironmentVars(GRADLE_OPTS: "-Djava.vendor=hollywood -Dsun.sunny=california").withTasks("verify").run()
+        executer.withGradleOpts("-Djava.vendor=hollywood", "-Dsun.sunny=california").withTasks("verify").run()
     }
 
     def "other client JVM system properties are carried over to daemon JVM"() {
         given:
-        distribution.file("build.gradle") << """
+        file("build.gradle") << """
 task verify << {
     assert System.getProperty("foo.bar") == "baz"
 }
         """
 
         expect:
-        executer.withEnvironmentVars(GRADLE_OPTS: "-Dfoo.bar=baz").withTasks("verify").run()
+        executer.withGradleOpts("-Dfoo.bar=baz").withTasks("verify").run()
 
     }
 }
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DispachingFailureIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DispachingFailureIntegrationSpec.groovy
index 75d7fd0..19ce0df 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DispachingFailureIntegrationSpec.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DispachingFailureIntegrationSpec.groovy
@@ -24,8 +24,8 @@ class DispachingFailureIntegrationSpec extends DaemonIntegrationSpec {
     def "failing build does not make the daemon send corrupted message"() {
         expect:
         //This kind of failure more likely reproduces the problem
-        def settingsFile = distribution.file("settings.gradle") << "// empty"
-        def projectdir = distribution.file("project dir").createDir()
+        def settingsFile = file("settings.gradle") << "// empty"
+        def projectdir = file("project dir").createDir()
 
         //requesting x failing builds creates enough stress to expose issues with unsynchronized dispatch
         50.times {
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy
new file mode 100644
index 0000000..9cd957c
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.launcher.daemon
+
+import org.gradle.api.logging.LogLevel
+import org.gradle.configuration.GradleLauncherMetaData
+import org.gradle.launcher.daemon.client.DaemonClient
+import org.gradle.launcher.daemon.client.EmbeddedDaemonClientServices
+import org.gradle.launcher.exec.DefaultBuildActionParameters
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.tooling.internal.provider.ConfiguringBuildAction
+import org.gradle.tooling.internal.provider.ExecuteBuildAction
+import org.junit.Rule
+import spock.lang.Specification
+
+/**
+ * Exercises the basic mechanics using an embedded daemon.
+ * 
+ * @todo Test stdio (what should println do in the daemon threads?)
+ * @todo launching multiple embedded daemons with the same registry
+ */
+class EmbeddedDaemonSmokeTest extends Specification {
+
+    @Rule TestNameTestDirectoryProvider temp
+
+    def daemonClientServices = new EmbeddedDaemonClientServices()
+
+    def "run build"() {
+        given:
+        def action = new ConfiguringBuildAction(projectDirectory: temp.testDirectory, searchUpwards: false, tasks: ['echo'],
+                gradleUserHomeDir: temp.createDir("user-home"), action: new ExecuteBuildAction())
+        def parameters = new DefaultBuildActionParameters(new GradleLauncherMetaData(), new Date().time, System.properties, System.getenv(), temp.testDirectory, LogLevel.LIFECYCLE)
+        
+        and:
+        def outputFile = temp.file("output.txt")
+        
+        expect:
+        !outputFile.exists()
+        
+        and:
+        temp.file("build.gradle") << """
+            task echo << {
+                file("output.txt").write "Hello!"
+            }
+        """
+        
+        when:
+        daemonClientServices.get(DaemonClient).execute(action, parameters)
+        
+        then:
+        outputFile.exists() && outputFile.text == "Hello!"
+    }
+    
+    def cleanup() {
+        daemonClientServices?.close()
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/SingleUseDaemonIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/SingleUseDaemonIntegrationTest.groovy
index 76086ed..4ff7d4f 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/SingleUseDaemonIntegrationTest.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/SingleUseDaemonIntegrationTest.groovy
@@ -18,17 +18,22 @@ package org.gradle.launcher.daemon
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
 import org.gradle.util.TextUtil
+import org.spockframework.runtime.SpockAssertionError
+import org.spockframework.runtime.SpockTimeoutError
 import spock.lang.IgnoreIf
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import spock.util.concurrent.PollingConditions
 
- at IgnoreIf({ GradleDistributionExecuter.systemPropertyExecuter == GradleDistributionExecuter.Executer.daemon })
+ at IgnoreIf({ GradleContextualExecuter.isDaemon() })
 class SingleUseDaemonIntegrationTest extends AbstractIntegrationSpec {
+    PollingConditions pollingConditions = new PollingConditions()
+
     def setup() {
         // Need forking executer
         // '-ea' is always set on the forked process. So I've added it explicitly here. // TODO:DAZ Clean this up
-        executer.withForkingExecuter().withEnvironmentVars(["JAVA_OPTS": "-ea"])
-        distribution.requireIsolatedDaemons()
+        executer.requireGradleHome(true).withEnvironmentVars(["JAVA_OPTS": "-ea"])
+        executer.requireIsolatedDaemons()
     }
 
     def "stops single use daemon on build complete"() {
@@ -41,8 +46,22 @@ class SingleUseDaemonIntegrationTest extends AbstractIntegrationSpec {
 
         then:
         wasForked()
+
         and:
-        executer.getDaemonRegistry().all.empty
+        noDaemonsRunning()
+    }
+
+    protected void noDaemonsRunning() {
+        // Because of GRADLE-2630, we need to use a spin assert here
+        // This should be removed when this bug is fixed.
+        try {
+            pollingConditions.eventually {
+                executer.getDaemonRegistry().all.empty
+            }
+        } catch (SpockTimeoutError e) {
+            // Spock swallows the inner exception, this is just to give a more helpful error message
+            throw new SpockAssertionError("The daemon registry is not empty after timeout (means daemons are still running)", e)
+        }
     }
 
     def "stops single use daemon when build fails"() {
@@ -58,10 +77,10 @@ class SingleUseDaemonIntegrationTest extends AbstractIntegrationSpec {
         failureHasCause "bad"
 
         and:
-        executer.getDaemonRegistry().all.empty
+        noDaemonsRunning()
     }
 
-    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null})
+    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null })
     def "does not fork build if java home from gradle properties matches current process"() {
         def alternateJavaHome = AvailableJavaHomes.bestAlternative
 
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/StoppingDaemonIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/StoppingDaemonIntegrationSpec.groovy
index f665159..c370bcf 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/StoppingDaemonIntegrationSpec.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/StoppingDaemonIntegrationSpec.groovy
@@ -17,7 +17,7 @@
 package org.gradle.launcher.daemon
 
 import org.gradle.launcher.daemon.logging.DaemonMessages
-import org.gradle.tests.fixtures.ConcurrentTestUtil
+import org.gradle.test.fixtures.ConcurrentTestUtil
 import org.gradle.util.TextUtil
 /**
  * by Szczepan Faber, created at: 1/20/12
@@ -25,15 +25,14 @@ import org.gradle.util.TextUtil
 class StoppingDaemonIntegrationSpec extends DaemonIntegrationSpec {
     def "can handle multiple concurrent stop requests"() {
         given:
-        def projectDir = distribution.testDir
-        projectDir.file('build.gradle') << '''
+        file('build.gradle') << '''
 file('marker.txt') << 'waiting'
 Thread.sleep(60000)
 '''
 
         when:
         def build = executer.start()
-        ConcurrentTestUtil.poll(20) { assert projectDir.file('marker.txt').file }
+        ConcurrentTestUtil.poll(20) { assert file('marker.txt').file }
 
         def stopExecutions = []
         5.times { idx ->
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServicesSupport.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServicesSupport.java
index c69d113..5df35c2 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServicesSupport.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServicesSupport.java
@@ -30,7 +30,6 @@ import org.gradle.launcher.daemon.context.DaemonContext;
 import org.gradle.launcher.daemon.context.DaemonContextBuilder;
 import org.gradle.launcher.daemon.registry.DaemonRegistry;
 import org.gradle.logging.internal.OutputEventListener;
-import org.gradle.messaging.remote.internal.DefaultMessageSerializer;
 import org.gradle.messaging.remote.internal.OutgoingConnector;
 import org.gradle.messaging.remote.internal.inet.TcpOutgoingConnector;
 import org.gradle.internal.id.CompositeIdGenerator;
@@ -98,8 +97,8 @@ abstract public class DaemonClientServicesSupport extends DefaultServiceRegistry
         return new CompositeIdGenerator(new UUIDGenerator().generateId(), new LongIdGenerator());
     }
 
-    protected OutgoingConnector<Object> createOutgoingConnector() {
-        return new TcpOutgoingConnector<Object>(new DefaultMessageSerializer<Object>(getClass().getClassLoader()));
+    protected OutgoingConnector createOutgoingConnector() {
+        return new TcpOutgoingConnector();
     }
 
     protected DaemonConnector createDaemonConnector() {
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonConnector.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonConnector.java
index e6661dd..2e38c85 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonConnector.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonConnector.java
@@ -38,11 +38,11 @@ public class DefaultDaemonConnector implements DaemonConnector {
     private static final Logger LOGGER = Logging.getLogger(DefaultDaemonConnector.class);
     public static final int DEFAULT_CONNECT_TIMEOUT = 30000;
     private final DaemonRegistry daemonRegistry;
-    private final OutgoingConnector<Object> connector;
+    protected final OutgoingConnector connector;
     private final DaemonStarter daemonStarter;
     private long connectTimeout = DefaultDaemonConnector.DEFAULT_CONNECT_TIMEOUT;
 
-    public DefaultDaemonConnector(DaemonRegistry daemonRegistry, OutgoingConnector<Object> connector, DaemonStarter daemonStarter) {
+    public DefaultDaemonConnector(DaemonRegistry daemonRegistry, OutgoingConnector connector, DaemonStarter daemonStarter) {
         this.daemonRegistry = daemonRegistry;
         this.connector = connector;
         this.daemonStarter = daemonStarter;
@@ -145,7 +145,7 @@ public class DefaultDaemonConnector implements DaemonConnector {
         };
         Connection<Object> connection;
         try {
-            connection = connector.connect(daemonInfo.getAddress());
+            connection = connector.connect(daemonInfo.getAddress(), getClass().getClassLoader());
         } catch (ConnectException e) {
             onFailure.run();
             throw e;
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonStarter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonStarter.java
index 6439db4..40196ae 100755
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonStarter.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonStarter.java
@@ -31,6 +31,7 @@ import org.gradle.process.ExecResult;
 import org.gradle.process.internal.ExecHandle;
 import org.gradle.util.Clock;
 import org.gradle.util.CollectionUtils;
+import org.gradle.util.GFileUtils;
 import org.gradle.util.GradleVersion;
 
 import java.io.File;
@@ -95,7 +96,7 @@ public class DefaultDaemonStarter implements DaemonStarter {
         LOGGER.info("Starting daemon process: workingDir = {}, daemonArgs: {}", workingDir, args);
         Clock clock = new Clock();
         try {
-            workingDir.mkdirs();
+            GFileUtils.mkdirs(workingDir);
 
             DaemonOutputConsumer outputConsumer = new DaemonOutputConsumer();
             ExecHandle handle = new DaemonExecHandleBuilder().build(args, workingDir, outputConsumer);
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonParameters.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonParameters.java
index d84f73b..2e6bf8f 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonParameters.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonParameters.java
@@ -42,6 +42,7 @@ public class DaemonParameters {
     public static final String JVM_ARGS_SYS_PROPERTY = "org.gradle.jvmargs";
     public static final String JAVA_HOME_SYS_PROPERTY = "org.gradle.java.home";
     public static final String DAEMON_SYS_PROPERTY = "org.gradle.daemon";
+    public static final String DEBUG_SYS_PROPERTY = "org.gradle.debug";
     static final int DEFAULT_IDLE_TIMEOUT = 3 * 60 * 60 * 1000;
     private final String uid;
     private File baseDir = new File(StartParameter.DEFAULT_GRADLE_USER_HOME, "daemon");
@@ -205,5 +206,10 @@ public class DaemonParameters {
                 throw new GradleException(String.format("Java home supplied via '%s' seems to be invalid: %s", JAVA_HOME_SYS_PROPERTY, propertyValue));
             }
         }
+
+        propertyValue = properties.get(DEBUG_SYS_PROPERTY);
+        if (propertyValue != null) {
+            jvmOptions.setDebug(propertyValue.toString().equalsIgnoreCase("true"));
+        }
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DaemonContextBuilder.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DaemonContextBuilder.java
index 6a70eaa..aef3381 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DaemonContextBuilder.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DaemonContextBuilder.java
@@ -17,9 +17,9 @@ package org.gradle.launcher.daemon.context;
 
 import com.google.common.collect.Lists;
 import org.gradle.internal.Factory;
+import org.gradle.internal.jvm.Jvm;
 import org.gradle.internal.nativeplatform.ProcessEnvironment;
 import org.gradle.launcher.daemon.configuration.DaemonParameters;
-import org.gradle.internal.jvm.Jvm;
 
 import java.io.File;
 import java.util.List;
@@ -55,7 +55,7 @@ public class DaemonContextBuilder implements Factory<DaemonContext> {
     }
 
     public File getDaemonRegistryDir() {
-        return this.daemonRegistryDir = daemonRegistryDir;
+        return this.daemonRegistryDir;
     }
 
     public void setDaemonRegistryDir(File daemonRegistryDir) {
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonDir.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonDir.java
index 3da23f9..f9ffddf 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonDir.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonDir.java
@@ -16,6 +16,7 @@
 
 package org.gradle.launcher.daemon.registry;
 
+import org.gradle.util.GFileUtils;
 import org.gradle.util.GradleVersion;
 
 import java.io.File;
@@ -29,7 +30,7 @@ public class DaemonDir {
         this.baseDir = baseDir;
         this.versionedDir = new File(baseDir, String.format("%s", GradleVersion.current().getVersion()));
         this.registryFile = new File(versionedDir, "registry.bin");
-        this.versionedDir.mkdirs();
+        GFileUtils.mkdirs(this.versionedDir);
     }
 
     public File getBaseDir() {
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/PersistentDaemonRegistry.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/PersistentDaemonRegistry.java
index 7ece979..6bf935d 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/PersistentDaemonRegistry.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/PersistentDaemonRegistry.java
@@ -18,7 +18,7 @@ package org.gradle.launcher.daemon.registry;
 
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
-import org.gradle.cache.DefaultSerializer;
+import org.gradle.messaging.serialize.DefaultSerializer;
 import org.gradle.cache.PersistentStateCache;
 import org.gradle.cache.internal.FileIntegrityViolationSuppressingPersistentStateCacheDecorator;
 import org.gradle.cache.internal.FileLockManager;
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonTcpServerConnector.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonTcpServerConnector.java
index f9d7bfe..7cedcc7 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonTcpServerConnector.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonTcpServerConnector.java
@@ -16,12 +16,14 @@
 package org.gradle.launcher.daemon.server;
 
 import org.gradle.api.Action;
+import org.gradle.internal.CompositeStoppable;
 import org.gradle.internal.concurrent.DefaultExecutorFactory;
 import org.gradle.internal.id.UUIDGenerator;
 import org.gradle.messaging.remote.Address;
 import org.gradle.messaging.remote.ConnectEvent;
 import org.gradle.messaging.remote.internal.Connection;
-import org.gradle.messaging.remote.internal.DefaultMessageSerializer;
+import org.gradle.messaging.remote.ConnectionAcceptor;
+import org.gradle.messaging.remote.internal.IncomingConnector;
 import org.gradle.messaging.remote.internal.inet.InetAddressFactory;
 import org.gradle.messaging.remote.internal.inet.TcpIncomingConnector;
 
@@ -32,16 +34,16 @@ import java.util.concurrent.locks.ReentrantLock;
  * Opens a TCP connection for clients to connect to to communicate with a daemon.
  */
 public class DaemonTcpServerConnector implements DaemonServerConnector {
-    final private TcpIncomingConnector<Object> incomingConnector;
+    final private IncomingConnector incomingConnector;
 
     private boolean started;
     private boolean stopped;
     private final Lock lifecycleLock = new ReentrantLock();
+    private ConnectionAcceptor acceptor;
 
     public DaemonTcpServerConnector() {
-        this.incomingConnector = new TcpIncomingConnector<Object>(
+        this.incomingConnector = new TcpIncomingConnector(
                 new DefaultExecutorFactory(),
-                new DefaultMessageSerializer<Object>(getClass().getClassLoader()),
                 new InetAddressFactory(),
                 new UUIDGenerator()
         );
@@ -66,9 +68,9 @@ public class DaemonTcpServerConnector implements DaemonServerConnector {
                 }
             };
 
-            Address address = incomingConnector.accept(connectEvent, false);
+            acceptor = incomingConnector.accept(connectEvent, getClass().getClassLoader(), false);
             started = true;
-            return address;
+            return acceptor.getAddress();
         } finally {
             lifecycleLock.unlock();
         }
@@ -76,13 +78,13 @@ public class DaemonTcpServerConnector implements DaemonServerConnector {
 
     public void stop() {
         lifecycleLock.lock();
-        try { // can't imagine what would go wrong here, but try/finally just in case
+        try {
             stopped = true;
         } finally {
             lifecycleLock.unlock();
         }
 
-        incomingConnector.stop();
+        CompositeStoppable.stoppable(acceptor, incomingConnector).stop();
     }
 
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/LogToClient.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/LogToClient.java
index 4ba0a4b..9d6c795 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/LogToClient.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/LogToClient.java
@@ -42,7 +42,7 @@ class LogToClient extends BuildCommandOnly {
         OutputEventListener listener = new OutputEventListener() {
             public void onOutput(OutputEvent event) {
                 try {
-                    if (event.getLogLevel().compareTo(buildLogLevel) >= 0) {
+                    if (event.getLogLevel() != null && event.getLogLevel().compareTo(buildLogLevel) >= 0) {
                         execution.getConnection().logEvent(event);
                     }
                 } catch (Exception e) {
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ConfiguringBuildAction.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ConfiguringBuildAction.java
index 420f89e..13ce0a6 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ConfiguringBuildAction.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ConfiguringBuildAction.java
@@ -41,6 +41,9 @@ class ConfiguringBuildAction<T> implements GradleLauncherAction<T>, Initializati
     private File gradleUserHomeDir;
     private Boolean searchUpwards;
 
+    // Important that this is constructed on the client so that it has the right gradleHomeDir internally
+    private final StartParameter startParameterTemplate = new StartParameter();
+
     public ConfiguringBuildAction() {}
 
     public ConfiguringBuildAction(ProviderOperationParameters parameters, GradleLauncherAction<T> action) {
@@ -54,7 +57,7 @@ class ConfiguringBuildAction<T> implements GradleLauncherAction<T>, Initializati
     }
 
     public StartParameter configureStartParameter() {
-        StartParameter startParameter = new StartParameter();
+        StartParameter startParameter = startParameterTemplate.newInstance();
         startParameter.setProjectDir(projectDirectory);
         if (gradleUserHomeDir != null) {
             startParameter.setGradleUserHomeDir(gradleUserHomeDir);
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/BuildActionsFactoryTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/BuildActionsFactoryTest.groovy
index 5c12558..510fa04 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/BuildActionsFactoryTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/BuildActionsFactoryTest.groovy
@@ -27,8 +27,8 @@ import org.gradle.launcher.daemon.configuration.DaemonParameters
 import org.gradle.launcher.exec.InProcessGradleLauncherActionExecuter
 import org.gradle.logging.ProgressLoggerFactory
 import org.gradle.logging.internal.OutputEventListener
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.SetSystemProperties
-import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -36,7 +36,7 @@ class BuildActionsFactoryTest extends Specification {
     @Rule
     public final SetSystemProperties sysProperties = new SetSystemProperties();
     @Rule
-    TemporaryFolder tmpDir = new TemporaryFolder();
+    TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     final CommandLineConverter<StartParameter> startParameterConverter = Mock()
     final ServiceRegistry loggingServices = Mock()
     final BuildActionsFactory factory = new BuildActionsFactory(loggingServices, startParameterConverter)
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/CommandLineActionFactoryTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/CommandLineActionFactoryTest.groovy
index d29a4df..d504f7b 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/CommandLineActionFactoryTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/CommandLineActionFactoryTest.groovy
@@ -22,15 +22,15 @@ import org.gradle.cli.CommandLineParser
 import org.gradle.internal.Factory
 import org.gradle.internal.service.ServiceRegistry
 import org.gradle.launcher.bootstrap.ExecutionListener
+import org.gradle.logging.*
 import org.gradle.logging.internal.OutputEventListener
 import org.gradle.logging.internal.StreamingStyledTextOutput
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.GradleVersion
 import org.gradle.util.RedirectStdOutAndErr
 import org.gradle.util.SetSystemProperties
-import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
-import org.gradle.logging.*
 
 class CommandLineActionFactoryTest extends Specification {
     @Rule
@@ -38,7 +38,7 @@ class CommandLineActionFactoryTest extends Specification {
     @Rule
     public final SetSystemProperties sysProperties = new SetSystemProperties();
     @Rule
-    TemporaryFolder tmpDir = new TemporaryFolder();
+    TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     final ExecutionListener executionListener = Mock()
     final ServiceRegistry loggingServices = Mock()
     final CommandLineConverter<LoggingConfiguration> loggingConfigurationConverter = Mock()
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy
deleted file mode 100644
index 236c56a..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.launcher.daemon
-
-import org.gradle.api.logging.LogLevel
-import org.gradle.configuration.GradleLauncherMetaData
-import org.gradle.launcher.daemon.client.DaemonClient
-import org.gradle.launcher.daemon.client.EmbeddedDaemonClientServices
-import org.gradle.launcher.exec.DefaultBuildActionParameters
-import org.gradle.tooling.internal.provider.ConfiguringBuildAction
-import org.gradle.tooling.internal.provider.ExecuteBuildAction
-import org.gradle.util.TemporaryFolder
-import org.junit.Rule
-import spock.lang.Specification
-
-/**
- * Exercises the basic mechanics using an embedded daemon.
- * 
- * @todo Test stdio (what should println do in the daemon threads?)
- * @todo launching multiple embedded daemons with the same registry
- */
-class EmbeddedDaemonSmokeTest extends Specification {
-
-    @Rule TemporaryFolder temp
-
-    def daemonClientServices = new EmbeddedDaemonClientServices()
-
-    def "run build"() {
-        given:
-        def action = new ConfiguringBuildAction(projectDirectory: temp.dir, searchUpwards: false, tasks: ['echo'],
-                gradleUserHomeDir: temp.createDir("user-home"), action: new ExecuteBuildAction())
-        def parameters = new DefaultBuildActionParameters(new GradleLauncherMetaData(), new Date().time, System.properties, System.getenv(), temp.dir, LogLevel.LIFECYCLE)
-        
-        and:
-        def outputFile = temp.file("output.txt")
-        
-        expect:
-        !outputFile.exists()
-        
-        and:
-        temp.file("build.gradle") << """
-            task echo << {
-                file("output.txt").write "Hello!"
-            }
-        """
-        
-        when:
-        daemonClientServices.get(DaemonClient).execute(action, parameters)
-        
-        then:
-        outputFile.exists() && outputFile.text == "Hello!"
-    }
-    
-    def cleanup() {
-        daemonClientServices?.close()
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientServicesTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientServicesTest.groovy
index bc3887f..101a226 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientServicesTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientServicesTest.groovy
@@ -19,13 +19,13 @@ import org.gradle.launcher.daemon.configuration.DaemonParameters
 import org.gradle.launcher.daemon.registry.DaemonRegistry
 import org.gradle.launcher.daemon.registry.PersistentDaemonRegistry
 import org.gradle.logging.LoggingServiceRegistry
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 class DaemonClientServicesTest extends Specification {
-    @Rule TemporaryFolder tmp = new TemporaryFolder()
-    final DaemonParameters parameters = new DaemonParameters(baseDir: tmp.testDir)
+    @Rule TestNameTestDirectoryProvider tmp = new TestNameTestDirectoryProvider()
+    final DaemonParameters parameters = new DaemonParameters(baseDir: tmp.testDirectory)
     final DaemonClientServices services = new DaemonClientServices(LoggingServiceRegistry.newEmbeddableLogging(), parameters, System.in)
 
     def "makes a DaemonRegistry available"() {
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DefaultDaemonConnectorTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DefaultDaemonConnectorTest.groovy
index 957388f..708e958 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DefaultDaemonConnectorTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DefaultDaemonConnectorTest.groovy
@@ -23,7 +23,9 @@ import org.gradle.launcher.daemon.context.DefaultDaemonContext
 import org.gradle.launcher.daemon.diagnostics.DaemonStartupInfo
 import org.gradle.launcher.daemon.registry.EmbeddedDaemonRegistry
 import org.gradle.messaging.remote.Address
+import org.gradle.messaging.remote.internal.ConnectException
 import org.gradle.messaging.remote.internal.Connection
+import org.gradle.messaging.remote.internal.MessageSerializer
 import org.gradle.messaging.remote.internal.OutgoingConnector
 import spock.lang.Specification
 
@@ -33,14 +35,16 @@ class DefaultDaemonConnectorTest extends Specification {
     def connectTimeoutSecs = 1
     def daemonCounter = 0
 
-    def createOutgoingConnector() {
-        new OutgoingConnector() {
-            Connection connect(Address address) {
-                def connection = [:] as Connection
-                // unsure why I can't add this as property in the map-mock above
-                connection.metaClass.num = address.num
-                connection
-            }
+    class OutgoingConnectorStub implements OutgoingConnector {
+        Connection connect(Address address, ClassLoader messageClassLoader) throws ConnectException {
+            def connection = [:] as Connection
+            // unsure why I can't add this as property in the map-mock above
+            connection.metaClass.num = address.num
+            connection
+        }
+
+        Connection connect(Address address, MessageSerializer serializer) {
+            throw new UnsupportedOperationException()
         }
     }
 
@@ -53,10 +57,10 @@ class DefaultDaemonConnectorTest extends Specification {
     }
 
     def createConnector() {
-        def connector = new DefaultDaemonConnector(
+        def connector = Spy(DefaultDaemonConnector, constructorArgs: [
                 new EmbeddedDaemonRegistry(),
-                createOutgoingConnector(),
-                { startBusyDaemon() } as DaemonStarter
+                Spy(OutgoingConnectorStub),
+                { startBusyDaemon() } as DaemonStarter]
         )
         connector.connectTimeout = connectTimeoutSecs * 1000
         connector
@@ -111,16 +115,20 @@ class DefaultDaemonConnectorTest extends Specification {
         connection && connection.connection.num < 12
     }
 
-    def "created connection removes from registry on failure"() {
+    def "suspect address is removed from the registry on connect failure"() {
         given:
         startIdleDaemon()
+        assert !registry.all.empty
+
+        connector.connector.connect(_ as Address, _) >> { throw new ConnectException("Problem!", new RuntimeException("foo")) }
 
         when:
         def connection = connector.maybeConnect( { true } as ExplainingSpec)
-        connection.onFailure.run()
 
         then:
-        registry.remove( _ as Address )
+        !connection
+
+        registry.all.empty
     }
 
     def "maybeConnect() returns null when no daemon matches spec"() {
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/CurrentProcessTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/CurrentProcessTest.groovy
index 8786b61..3e6089e 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/CurrentProcessTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/CurrentProcessTest.groovy
@@ -14,18 +14,17 @@
  * limitations under the License.
  */
 
-package org.gradle.launcher.daemon.configuration;
-
+package org.gradle.launcher.daemon.configuration
 
 import org.gradle.api.internal.file.FileResolver
 import org.gradle.process.internal.JvmOptions
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.SetSystemProperties
-import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
 
 public class CurrentProcessTest extends Specification {
-    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     @Rule final SetSystemProperties systemPropertiesSet = new SetSystemProperties()
     private FileResolver fileResolver = Mock()
     private def currentJavaHome = tmpDir.file('java_home')
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/DaemonParametersTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/DaemonParametersTest.groovy
index e1c88e0..a1ead12 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/DaemonParametersTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/DaemonParametersTest.groovy
@@ -18,12 +18,12 @@ package org.gradle.launcher.daemon.configuration
 import org.gradle.StartParameter
 import org.gradle.api.GradleException
 import org.gradle.internal.jvm.Jvm
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 class DaemonParametersTest extends Specification {
-    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final DaemonParameters parameters = new DaemonParameters()
 
     def "has reasonable default values"() {
@@ -109,7 +109,7 @@ class DaemonParametersTest extends Specification {
         }
 
         when:
-        parameters.configureFromBuildDir(tmpDir.dir, true)
+        parameters.configureFromBuildDir(tmpDir.testDirectory, true)
 
         then:
         parameters.effectiveJvmArgs.contains('-Xmx1024m')
@@ -126,7 +126,7 @@ class DaemonParametersTest extends Specification {
         }
 
         when:
-        parameters.configureFromBuildDir(tmpDir.dir, true)
+        parameters.configureFromBuildDir(tmpDir.testDirectory, true)
 
         then:
         parameters.idleTimeout == 1450
@@ -139,7 +139,7 @@ class DaemonParametersTest extends Specification {
         }
 
         when:
-        parameters.configureFromGradleUserHome(tmpDir.dir)
+        parameters.configureFromGradleUserHome(tmpDir.testDirectory)
 
         then:
         parameters.effectiveJvmArgs.contains('-Xmx1024m')
@@ -172,7 +172,7 @@ class DaemonParametersTest extends Specification {
         }
 
         when:
-        parameters.configureFromBuildDir(tmpDir.dir, true)
+        parameters.configureFromBuildDir(tmpDir.testDirectory, true)
 
         then:
         parameters.enabled
@@ -246,4 +246,16 @@ class DaemonParametersTest extends Specification {
         then:
         !parameters.usingDefaultJvmArgs
     }
+
+    def "can configure debug mode"() {
+        when:
+        parameters.configureFrom((DaemonParameters.DEBUG_SYS_PROPERTY): flag)
+
+        then:
+        parameters.effectiveJvmArgs.contains("-Xdebug") == Boolean.parseBoolean(flag)
+        parameters.effectiveJvmArgs.contains("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005") == Boolean.parseBoolean(flag)
+
+        where:
+        flag << ["true", "false"]
+    }
 }
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/context/DaemonCompatibilitySpecSpec.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/context/DaemonCompatibilitySpecSpec.groovy
index 3aae06a..72d2038 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/context/DaemonCompatibilitySpecSpec.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/context/DaemonCompatibilitySpecSpec.groovy
@@ -17,16 +17,16 @@ package org.gradle.launcher.daemon.context
 
 import org.gradle.internal.nativeplatform.ProcessEnvironment
 import org.gradle.internal.nativeplatform.filesystem.FileSystems
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.ConfigureUtil
 import org.gradle.util.Requires
-import org.gradle.util.TemporaryFolder
 import org.gradle.util.TestPrecondition
 import org.junit.Rule
 import spock.lang.Specification
 
 class DaemonCompatibilitySpecSpec extends Specification {
 
-    @Rule TemporaryFolder tmp = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider tmp = new TestNameTestDirectoryProvider()
 
     def clientConfigure = {}
     def serverConfigure = {}
@@ -79,7 +79,7 @@ class DaemonCompatibilitySpecSpec extends Specification {
     @Requires(TestPrecondition.SYMLINKS)
     def "contexts with symlinked javaHome are compatible"() {
         File dir = tmp.createDir("a")
-        File link = new File(tmp.dir, "link")
+        File link = new File(tmp.testDirectory, "link")
         FileSystems.default.createSymbolicLink(link, dir)
 
         assert dir != link
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/diagnostics/DaemonDiagnosticsTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/diagnostics/DaemonDiagnosticsTest.groovy
index 194da07..1a9c3a3 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/diagnostics/DaemonDiagnosticsTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/diagnostics/DaemonDiagnosticsTest.groovy
@@ -16,7 +16,7 @@
 
 package org.gradle.launcher.daemon.diagnostics
 
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -25,7 +25,7 @@ import spock.lang.Specification
  */
 class DaemonDiagnosticsTest extends Specification {
 
-    @Rule TemporaryFolder temp
+    @Rule TestNameTestDirectoryProvider temp
 
     def "tailing the daemon log is always safe"() {
         given:
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/DaemonRegistryServicesTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/DaemonRegistryServicesTest.groovy
index 7a774f0..8c14629 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/DaemonRegistryServicesTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/DaemonRegistryServicesTest.groovy
@@ -17,13 +17,13 @@ package org.gradle.launcher.daemon.registry
 
 import org.gradle.launcher.daemon.context.DefaultDaemonContext
 import org.gradle.messaging.remote.internal.inet.SocketInetAddress
-import org.gradle.tests.fixtures.ConcurrentTestUtil
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.ConcurrentTestUtil
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 class DaemonRegistryServicesTest extends Specification {
-    @Rule TemporaryFolder tmp = new TemporaryFolder()
+    @Rule TestNameTestDirectoryProvider tmp = new TestNameTestDirectoryProvider()
 
     def registry(baseDir) {
         new DaemonRegistryServices(tmp.createDir(baseDir))
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/PersistentDaemonRegistryTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/PersistentDaemonRegistryTest.groovy
index d4305c8..449e3dd 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/PersistentDaemonRegistryTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/PersistentDaemonRegistryTest.groovy
@@ -20,7 +20,7 @@ import org.gradle.internal.nativeplatform.ProcessEnvironment
 import org.gradle.launcher.daemon.context.DaemonContext
 import org.gradle.launcher.daemon.context.DaemonContextBuilder
 import org.gradle.messaging.remote.Address
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -29,7 +29,7 @@ import static org.gradle.cache.internal.DefaultFileLockManagerTestHelper.unlockU
 
 class PersistentDaemonRegistryTest extends Specification {
 
-    @Rule TemporaryFolder tmp
+    @Rule TestNameTestDirectoryProvider tmp
     
     int addressCounter = 0
     def lockManager = createDefaultFileLockManager()
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServerExceptionHandlingTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServerExceptionHandlingTest.groovy
index 0b862db..924b9e1 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServerExceptionHandlingTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServerExceptionHandlingTest.groovy
@@ -18,6 +18,7 @@ package org.gradle.launcher.daemon.server
 
 import org.gradle.BuildResult
 import org.gradle.GradleLauncher
+import org.gradle.api.logging.LogLevel
 import org.gradle.configuration.GradleLauncherMetaData
 import org.gradle.initialization.DefaultGradleLauncherFactory
 import org.gradle.initialization.GradleLauncherAction
@@ -31,18 +32,17 @@ import org.gradle.launcher.daemon.server.exec.DefaultDaemonCommandExecuter
 import org.gradle.launcher.daemon.server.exec.ForwardClientInput
 import org.gradle.launcher.exec.DefaultBuildActionParameters
 import org.gradle.logging.LoggingManagerInternal
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
-import org.gradle.api.logging.LogLevel
 
 /**
  * by Szczepan Faber, created at: 12/21/11
  */
 class DaemonServerExceptionHandlingTest extends Specification {
 
-    @Rule TemporaryFolder temp = new TemporaryFolder()
-    def parameters = new DefaultBuildActionParameters(new GradleLauncherMetaData(), 0, new HashMap(System.properties), [:], temp.dir, LogLevel.ERROR)
+    @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+    def parameters = new DefaultBuildActionParameters(new GradleLauncherMetaData(), 0, new HashMap(System.properties), [:], temp.testDirectory, LogLevel.ERROR)
 
     static class DummyLauncherAction implements GradleLauncherAction, Serializable {
         Object someState
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServicesTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServicesTest.groovy
index 42a0d6a..d6cc441 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServicesTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServicesTest.groovy
@@ -20,15 +20,16 @@ import org.gradle.launcher.daemon.configuration.DefaultDaemonServerConfiguration
 import org.gradle.launcher.daemon.registry.DaemonDir
 import org.gradle.logging.LoggingManagerInternal
 import org.gradle.logging.LoggingServiceRegistry
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
+
 import static java.util.Arrays.asList
 
 class DaemonServicesTest extends Specification {
 
-    @Rule TemporaryFolder tmp = new TemporaryFolder()
-    final DaemonServices services = new DaemonServices(new DefaultDaemonServerConfiguration("uid", tmp.testDir, 100, asList()),
+    @Rule TestNameTestDirectoryProvider tmp = new TestNameTestDirectoryProvider()
+    final DaemonServices services = new DaemonServices(new DefaultDaemonServerConfiguration("uid", tmp.testDirectory, 100, asList()),
             LoggingServiceRegistry.newEmbeddableLogging(), Mock(LoggingManagerInternal))
 
     def "makes a DaemonDir available"() {
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ConfiguringBuildActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ConfiguringBuildActionTest.groovy
index 08023de..e5c706f 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ConfiguringBuildActionTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ConfiguringBuildActionTest.groovy
@@ -18,8 +18,7 @@
 
 package org.gradle.tooling.internal.provider
 
-import org.gradle.StartParameter
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -27,7 +26,7 @@ import spock.lang.Specification
  * by Szczepan Faber, created at: 3/6/12
  */
 class ConfiguringBuildActionTest extends Specification {
-    @Rule TemporaryFolder temp
+    @Rule TestNameTestDirectoryProvider temp
 
     def "allows configuring the start parameter with build arguments"() {
         when:
diff --git a/subprojects/maven/maven.gradle b/subprojects/maven/maven.gradle
index 4a8fa81..0e62608 100644
--- a/subprojects/maven/maven.gradle
+++ b/subprojects/maven/maven.gradle
@@ -20,6 +20,7 @@ dependencies {
     compile project(':core')
     compile project(':coreImpl')
     compile project(':plugins')
+    compile project(':publish')
     compile libraries.slf4j_api
 
     compile libraries.maven_ant_tasks
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/AutoTestedSamplesMavenIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/AutoTestedSamplesMavenIntegrationTest.groovy
new file mode 100644
index 0000000..c67008b
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/AutoTestedSamplesMavenIntegrationTest.groovy
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.api
+
+import org.gradle.integtests.fixtures.AbstractAutoTestedSamplesTest
+import org.junit.Test
+
+class AutoTestedSamplesMavenIntegrationTest extends AbstractAutoTestedSamplesTest {
+
+    @Test
+    void runSamples() {
+        runSamplesFrom("subprojects/maven/src/main")
+    }
+
+}
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/plugins/maven/MavenConversionIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/plugins/maven/MavenConversionIntegrationTest.groovy
index 74a2ddc..7345d60 100644
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/plugins/maven/MavenConversionIntegrationTest.groovy
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/plugins/maven/MavenConversionIntegrationTest.groovy
@@ -17,11 +17,11 @@
 package org.gradle.api.plugins.maven
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.TestResources
 import org.junit.Rule
 
 import static org.gradle.util.TextUtil.toPlatformLineSeparators
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
 
 /**
  * by Szczepan Faber, created at: 9/4/12
@@ -49,7 +49,7 @@ class MavenConversionIntegrationTest extends AbstractIntegrationSpec {
         file("webinar-war/build/libs/webinar-war-1.0-SNAPSHOT.war").exists()
         file('webinar-impl/build/reports/tests/index.html').exists()
 
-        new JUnitTestExecutionResult(file("webinar-impl")).assertTestClassesExecuted('webinar.WebinarTest')
+        new DefaultTestExecutionResult(file("webinar-impl")).assertTestClassesExecuted('webinar.WebinarTest')
 
         when:
         run 'projects'
@@ -64,40 +64,40 @@ Root project 'webinar-parent'
     }
 
     def "flatmultimodule"() {
-            given:
-            file("webinar-parent/build.gradle") << "apply plugin: 'maven2Gradle'"
+        given:
+        file("webinar-parent/build.gradle") << "apply plugin: 'maven2Gradle'"
 
-            when:
-            executer.inDirectory(file("webinar-parent"))
-            run 'maven2Gradle'
+        when:
+        executer.inDirectory(file("webinar-parent"))
+        run 'maven2Gradle'
 
-            then:
-            file("webinar-parent/settings.gradle").exists()
+        then:
+        file("webinar-parent/settings.gradle").exists()
 
-            when:
-            executer.inDirectory(file("webinar-parent"))
-            run '-i', 'clean', 'build'
+        when:
+        executer.inDirectory(file("webinar-parent"))
+        run '-i', 'clean', 'build'
 
-            then: //smoke test the build artifacts
-            file("webinar-api/build/libs/webinar-api-1.0-SNAPSHOT.jar").exists()
-            file("webinar-impl/build/libs/webinar-impl-1.0-SNAPSHOT.jar").exists()
-            file("webinar-war/build/libs/webinar-war-1.0-SNAPSHOT.war").exists()
-            file('webinar-impl/build/reports/tests/index.html').exists()
+        then: //smoke test the build artifacts
+        file("webinar-api/build/libs/webinar-api-1.0-SNAPSHOT.jar").exists()
+        file("webinar-impl/build/libs/webinar-impl-1.0-SNAPSHOT.jar").exists()
+        file("webinar-war/build/libs/webinar-war-1.0-SNAPSHOT.war").exists()
+        file('webinar-impl/build/reports/tests/index.html').exists()
 
-            new JUnitTestExecutionResult(file("webinar-impl")).assertTestClassesExecuted('webinar.WebinarTest')
+        new DefaultTestExecutionResult(file("webinar-impl")).assertTestClassesExecuted('webinar.WebinarTest')
 
-            when:
-            executer.inDirectory(file("webinar-parent"))
-            run 'projects'
+        when:
+        executer.inDirectory(file("webinar-parent"))
+        run 'projects'
 
-            then:
-            output.contains(toPlatformLineSeparators("""
+        then:
+        output.contains(toPlatformLineSeparators("""
 Root project 'webinar-parent'
 +--- Project ':webinar-api' - Webinar APIs
 +--- Project ':webinar-impl' - Webinar implementation
 \\--- Project ':webinar-war' - Webinar web application
 """))
-        }
+    }
 
     def "singleModule"() {
         given:
@@ -136,4 +136,26 @@ Root project 'webinar-parent'
         file("build/libs/testjar-2.5.jar").exists()
         file("build/libs/testjar-2.5-tests.jar").exists()
     }
+
+    def "enforcerplugin"() {
+        given:
+        file("build.gradle") << "apply plugin: 'maven2Gradle'"
+
+        when:
+        run 'maven2Gradle'
+
+        then:
+        noExceptionThrown()
+        and:
+        buildFile.text.contains("""configurations.all {
+it.exclude group: 'org.apache.maven'
+it.exclude group: 'org.apache.maven', module: 'badArtifact'
+it.exclude group: '*', module: 'badArtifact'
+}""")
+        when:
+        run 'clean', 'build'
+
+        then:
+        file("build/libs/enforcerExample-1.0.jar").exists()
+    }
 }
\ No newline at end of file
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishBasicIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishBasicIntegTest.groovy
new file mode 100644
index 0000000..91d8af7
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishBasicIntegTest.groovy
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.maven.M2Installation
+import org.gradle.test.fixtures.maven.MavenFileRepository
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+
+/**
+ * Tests “simple” maven publishing scenarios
+ */
+class MavenPublishBasicIntegTest extends AbstractIntegrationSpec {
+    @Rule SetSystemProperties sysProp = new SetSystemProperties()
+
+    M2Installation m2Installation
+    MavenFileRepository m2Repo
+
+    def "setup"() {
+        m2Installation = new M2Installation(testDirectory)
+        using m2Installation
+        m2Repo = m2Installation.mavenRepo()
+    }
+
+    def "publishes nothing without component"() {
+        given:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+
+            group = 'group'
+            version = '1.0'
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+            }
+        """
+        when:
+        succeeds 'publish'
+
+        then:
+        mavenRepo.module('group', 'root', '1.0').assertNotPublished()
+    }
+
+    def "can publish simple jar"() {
+        given:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+            apply plugin: 'java'
+
+            group = 'group'
+            version = '1.0'
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+            }
+        """
+
+        when:
+        succeeds 'assemble'
+
+        then: "jar is built but not published"
+        mavenRepo.module('group', 'root', '1.0').assertNotPublished()
+        m2Repo.module('group', 'root', '1.0').assertNotPublished()
+        file('build/libs/root-1.0.jar').assertExists()
+
+        when:
+        succeeds 'publish'
+
+        then: "jar is published to defined maven repository"
+        mavenRepo.module('group', 'root', '1.0').assertPublishedAsJavaModule()
+        m2Repo.module('group', 'root', '1.0').assertNotPublished()
+
+        when:
+        succeeds 'publishToMavenLocal'
+
+        then: "jar is published to maven local repository"
+        m2Repo.module('group', 'root', '1.0').assertPublishedAsJavaModule()
+    }
+}
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishCrossVersionIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishCrossVersionIntegrationTest.groovy
new file mode 100644
index 0000000..1c704ac
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishCrossVersionIntegrationTest.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.publish.maven
+
+import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
+import org.gradle.test.fixtures.maven.MavenFileRepository
+
+class MavenPublishCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
+
+    final MavenFileRepository repo = new MavenFileRepository(file("maven-repo"))
+
+    def "maven publication generated by maven-publish plugin can be consumed by previous versions of Gradle"() {
+        given:
+        projectPublishedUsingMavenPublishPlugin()
+
+        expect:
+        canConsumePublicationWithPreviousVersion()
+    }
+
+    def projectPublishedUsingMavenPublishPlugin() {
+        settingsFile.text = "rootProject.name = 'published'"
+
+        buildFile.text = """
+apply plugin: 'java'
+apply plugin: 'maven-publish'
+
+group = 'org.gradle.crossversion'
+version = '1.9'
+
+repositories {
+    mavenCentral()
+}
+dependencies {
+    compile "commons-collections:commons-collections:3.0"
+}
+publishing {
+    repositories {
+        maven { url "${repo.uri}" }
+    }
+}
+"""
+
+        version current withTasks 'publish' run()
+    }
+
+    def canConsumePublicationWithPreviousVersion() {
+        settingsFile.text = "rootProject.name = 'consumer'"
+
+        buildFile.text = """
+configurations {
+    lib
+}
+repositories {
+    mavenCentral()
+    mavenRepo(urls: ['${repo.uri}'])
+}
+dependencies {
+    lib 'org.gradle.crossversion:published:1.9'
+}
+task retrieve(type: Copy) {
+    into 'build/resolved'
+    from configurations.lib
+}
+"""
+
+        version previous withDeprecationChecksDisabled() withTasks 'retrieve' run()
+
+        file('build/resolved').assertHasDescendants('published-1.9.jar', 'commons-collections-3.0.jar')
+    }
+}
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishHttpIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishHttpIntegTest.groovy
new file mode 100644
index 0000000..43721f9
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishHttpIntegTest.groovy
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven
+
+import org.gradle.api.internal.artifacts.repositories.DefaultPasswordCredentials
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
+import org.gradle.test.fixtures.maven.MavenHttpModule
+import org.gradle.test.fixtures.maven.MavenHttpRepository
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.hamcrest.Matchers
+import org.junit.Rule
+import spock.lang.Unroll
+
+class MavenPublishHttpIntegTest extends AbstractIntegrationSpec {
+
+    @Rule HttpServer server
+    @Rule ProgressLoggingFixture progressLogging
+
+    MavenHttpRepository mavenRemoteRepo
+    MavenHttpModule module
+
+    def repoPath = "/repo"
+    String group
+    String name
+    String version
+
+    def setup() {
+        server.start()
+
+        mavenRemoteRepo = new MavenHttpRepository(server, repoPath, mavenRepo)
+        group = "org.gradle"
+        name = "publish"
+        version = "2"
+        module = mavenRemoteRepo.module(group, name, version)
+
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'maven-publish'
+            version = '$version'
+            group = '$group'
+
+            publishing {
+                repositories {
+                    maven {
+                        url "$mavenRemoteRepo.uri"
+                    }
+                }
+            }
+        """
+    }
+
+    def "can publish to an unauthenticated http repo"() {
+        given:
+        module.artifact.expectPut()
+        module.artifact.sha1.expectPut()
+        module.artifact.md5.expectPut()
+        module.rootMetaData.expectGetMissing()
+        module.rootMetaData.expectPut()
+        module.rootMetaData.sha1.expectPut()
+        module.rootMetaData.md5.expectPut()
+        module.pom.expectPut()
+        module.pom.sha1.expectPut()
+        module.pom.md5.expectPut()
+        when:
+        succeeds 'publish'
+
+        then:
+        def localPom = file("build/publications/maven/pom-default.xml").assertIsFile()
+        def localArtifact = file("build/libs/publish-2.jar").assertIsFile()
+
+        module.pomFile.assertIsCopyOf(localPom)
+        module.pom.verifyChecksums()
+        module.artifactFile.assertIsCopyOf(localArtifact)
+        module.artifact.verifyChecksums()
+
+        module.rootMetaData.verifyChecksums()
+        module.rootMetaData.versions == ["2"]
+    }
+
+    @Unroll
+    def "can publish to authenticated repository using #authScheme auth"() {
+        given:
+        def credentials = new DefaultPasswordCredentials('username', 'password')
+
+        buildFile << """
+            publishing.repositories.maven.credentials {
+                username '${credentials.username}'
+                password '${credentials.password}'
+            }
+        """
+
+        server.authenticationScheme = authScheme
+
+        module.artifact.expectPut(credentials)
+        module.artifact.sha1.expectPut(credentials)
+        module.artifact.md5.expectPut(credentials)
+        module.rootMetaData.expectGetMissing(credentials)
+        module.rootMetaData.expectPut(credentials)
+        module.rootMetaData.sha1.expectPut(credentials)
+        module.rootMetaData.md5.expectPut(credentials)
+        module.pom.expectPut(credentials)
+        module.pom.sha1.expectPut(credentials)
+        module.pom.md5.expectPut(credentials)
+
+        when:
+        succeeds 'publish'
+
+        then:
+        def localPom = file("build/publications/maven/pom-default.xml").assertIsFile()
+        def localArtifact = file("build/libs/publish-2.jar").assertIsFile()
+
+        module.pomFile.assertIsCopyOf(localPom)
+        module.pom.verifyChecksums()
+        module.artifactFile.assertIsCopyOf(localArtifact)
+        module.artifact.verifyChecksums()
+
+        module.rootMetaData.verifyChecksums()
+        module.rootMetaData.versions == ["2"]
+
+        where:
+        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
+    }
+
+    @Unroll
+    def "reports failure publishing with wrong credentials using #authScheme"() {
+        given:
+        def credentials = new DefaultPasswordCredentials('wrong', 'wrong')
+
+        buildFile << """
+            publishing.repositories.maven.credentials {
+                username '${credentials.username}'
+                password '${credentials.password}'
+            }
+        """
+
+        server.authenticationScheme = authScheme
+        module.artifact.expectPut(401, credentials)
+
+        when:
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription('Execution failed for task \':publishMavenPublicationToMavenRepository\'.')
+        failure.assertHasCause('Failed to publish publication \'maven\' to repository \'maven\'')
+        failure.assertThatCause(Matchers.containsString('Return code is: 401'))
+
+        where:
+        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
+    }
+
+    @Unroll
+    def "reports failure when required credentials are not provided #authScheme"() {
+        given:
+        server.authenticationScheme = authScheme
+        module.artifact.expectPut(401)
+
+        when:
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription('Execution failed for task \':publishMavenPublicationToMavenRepository\'.')
+        failure.assertHasCause('Failed to publish publication \'maven\' to repository \'maven\'')
+        failure.assertThatCause(Matchers.containsString('Return code is: 401'))
+
+        where:
+        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
+    }
+}
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishJavaIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishJavaIntegTest.groovy
new file mode 100644
index 0000000..ef69409
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishJavaIntegTest.groovy
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+
+package org.gradle.api.publish.maven
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class MavenPublishJavaIntegTest extends AbstractIntegrationSpec {
+    public void "can publish jar and meta-data to maven repository"() {
+        given:
+        settingsFile << "rootProject.name = 'publishTest' "
+
+        and:
+        buildFile << """
+            apply plugin: 'maven-publish'
+            apply plugin: 'java'
+
+            group = 'org.gradle.test'
+            version = '1.9'
+
+            repositories {
+                mavenCentral()
+            }
+
+            dependencies {
+                compile "commons-collections:commons-collections:3.2.1"
+                runtime "commons-io:commons-io:1.4"
+                testCompile "junit:junit:4.11"
+            }
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+            }
+"""
+
+        when:
+        run "publish"
+
+        then:
+        def mavenModule = mavenRepo.module("org.gradle.test", "publishTest", "1.9")
+        mavenModule.assertPublishedAsJavaModule()
+
+        mavenModule.parsedPom.scopes.runtime.dependencies.size() == 2
+        mavenModule.parsedPom.scopes.runtime.assertDependsOn("commons-collections", "commons-collections", "3.2.1")
+        mavenModule.parsedPom.scopes.runtime.assertDependsOn("commons-io", "commons-io", "1.4")
+        mavenModule.parsedPom.scopes.compile == null
+        mavenModule.parsedPom.scopes.testCompile == null
+    }
+
+}
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishMultiProjectIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishMultiProjectIntegTest.groovy
new file mode 100644
index 0000000..3cdee56
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishMultiProjectIntegTest.groovy
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import spock.lang.Ignore
+
+class MavenPublishMultiProjectIntegTest extends AbstractIntegrationSpec {
+    def project1module = mavenRepo.module("org.gradle.test", "project1", "1.9")
+
+    def "project dependency correctly reflected in POM"() {
+        createBuildScripts("""
+project(":project1") {
+    dependencies {
+        compile project(":project2")
+    }
+}
+        """)
+
+        when:
+        run "publish"
+
+        then:
+        projectsCorrectlyPublished()
+    }
+
+    def "maven-publish plugin does not take archivesBaseName into account when publishing"() {
+        createBuildScripts("""
+project(":project1") {
+    dependencies {
+        compile project(":project2")
+    }
+}
+
+project(":project2") {
+    archivesBaseName = "changed"
+}
+        """)
+
+        when:
+        run "publish"
+
+        then:
+        projectsCorrectlyPublished()
+    }
+
+    def "maven-publish plugin does not take mavenDeployer.pom.artifactId into account when publishing"() {
+        createBuildScripts("""
+project(":project1") {
+    dependencies {
+        compile project(":project2")
+    }
+}
+
+project(":project2") {
+    apply plugin: 'maven'
+    uploadArchives {
+        repositories {
+            mavenDeployer {
+                repository(url: "file:///\$rootProject.projectDir/maven-repo")
+                pom.artifactId = "changed"
+            }
+        }
+    }
+}
+        """)
+
+        when:
+        run "publish"
+
+        then:
+        projectsCorrectlyPublished()
+    }
+
+    private def projectsCorrectlyPublished() {
+        def project2 = mavenRepo.module("org.gradle.test", "project2", "1.9")
+        project2.assertPublishedAsJavaModule()
+
+        def project1pom = project1module.parsedPom
+        project1pom.scopes.runtime.assertDependsOn("org.gradle.test", "project2", "1.9")
+
+        return true
+    }
+
+
+    def "maven-publish plugin will use target project archivesBaseName for project dependency when target project does not have maven-publish plugin applied"() {
+        given:
+        settingsFile << """
+include "project1", "project2"
+        """
+
+        buildFile << """
+allprojects {
+    group = "org.gradle.test"
+    version = 1.9
+}
+
+project(":project1") {
+    apply plugin: "java"
+    apply plugin: "maven-publish"
+
+    dependencies {
+        compile project(":project2")
+    }
+
+    publishing {
+        repositories {
+            maven { url "file:///\$rootProject.projectDir/maven-repo" }
+        }
+    }
+}
+project(":project2") {
+    apply plugin: 'maven'
+    archivesBaseName = "changed"
+}
+        """
+
+        when:
+        run "publish"
+
+        then:
+        def project1pom = project1module.parsedPom
+        project1pom.scopes.runtime.assertDependsOn("org.gradle.test", "changed", "1.9")
+    }
+
+    @Ignore("This does not work: fix this as part of making the project coordinates customisable via DSL") // TODO:DAZ
+    def "project dependency correctly reflected in POM if dependency publication pom is changed"() {
+        createBuildScripts("""
+project(":project1") {
+    dependencies {
+        compile project(":project2")
+    }
+}
+
+project(":project2") {
+    publishing {
+        publications {
+            maven {
+                pom.withXml {
+                    asNode().artifactId[0].value = "changed"
+                }
+            }
+        }
+    }
+}
+        """)
+
+        when:
+        run ":project1:publish"
+
+        then:
+        def pom = project1module.parsedPom
+        pom.scopes.runtime.assertDependsOn("org.gradle.test", "changed", "1.9")
+    }
+
+    def "multiple project dependencies correctly reflected in POMs"() {
+        createBuildScripts("""
+project(":project1") {
+    dependencies {
+        compile project(":project2")
+        compile project(":project3")
+    }
+}
+
+project(":project2") {
+    dependencies {
+        compile project(":project3")
+    }
+}
+        """)
+
+        when:
+        run "publish"
+
+        then:
+        def pom = project1module.parsedPom
+        pom.scopes.runtime.assertDependsOnArtifacts("project2", "project3")
+
+        and:
+        def pom2 = mavenRepo.module("org.gradle.test", "project2", "1.9").parsedPom
+        pom2.scopes.runtime.assertDependsOnArtifacts("project3")
+
+        and:
+        def pom3 = mavenRepo.module("org.gradle.test", "project3", "1.9").parsedPom
+        pom3.scopes.runtime == null
+    }
+
+    private void createBuildScripts(String append = "") {
+        settingsFile << """
+include "project1", "project2", "project3"
+        """
+
+        buildFile << """
+allprojects {
+    group = "org.gradle.test"
+    version = 1.9
+}
+
+subprojects {
+    apply plugin: "java"
+    apply plugin: "maven-publish"
+
+    publishing {
+        repositories {
+            maven { url "file:///\$rootProject.projectDir/maven-repo" }
+        }
+    }
+}
+
+$append
+        """
+    }
+}
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishPomCustomisationIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishPomCustomisationIntegTest.groovy
new file mode 100644
index 0000000..de92676
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishPomCustomisationIntegTest.groovy
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.api.publish.maven
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+/**
+ * Tests maven POM customisation
+ */
+class MavenPublishPomCustomisationIntegTest extends AbstractIntegrationSpec {
+    @Rule SetSystemProperties sysProp = new SetSystemProperties()
+
+    def "can customise pom xml"() {
+        given:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+            apply plugin: 'java'
+
+            group = 'group'
+            version = '1.0'
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    maven {
+                        pom.withXml {
+                            asNode().groupId[0].value = "changed-group"
+                            asNode().artifactId[0].value = "changed-artifact"
+                            asNode().version[0].value = "changed-version"
+                            asNode().appendNode('description', "custom-description")
+
+                            def dependency = asNode().appendNode('dependencies').appendNode('dependency')
+                            dependency.appendNode('groupId', 'junit')
+                            dependency.appendNode('artifactId', 'junit')
+                            dependency.appendNode('version', '4.11')
+                            dependency.appendNode('scope', 'runtime')
+                        }
+                    }
+                }
+            }
+        """
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module('changed-group', 'changed-artifact', 'changed-version')
+        module.assertPublishedAsJavaModule()
+        module.parsedPom.description == 'custom-description'
+        module.parsedPom.scopes.runtime.assertDependsOn("junit", "junit", "4.11")
+    }
+
+    def "pom can contain non-ascii characters"() {
+        // Group and Artifact are restricted to [A-Za-z0-9_\\-.]+ by org.apache.maven.project.validation.DefaultModelValidator
+        def groupId = 'group'
+        def artifactId = 'artifact'
+
+        // Try version & description with non-ascii characters
+        def version = 'version-₦ガき∆'
+        def description = 'description-ç√∫'
+
+        given:
+        settingsFile << "rootProject.name = '${artifactId}'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+            apply plugin: 'java'
+
+            group = '${groupId}'
+            version = '${version}'
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    maven {
+                        pom.withXml {
+                            asNode().appendNode('description', "${description}")
+                        }
+                    }
+                }
+            }
+        """
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module(groupId, artifactId, version)
+        module.assertPublishedAsJavaModule()
+        module.parsedPom.description == description
+    }
+
+    def "has reasonable error message when withXml fails"() {
+        given:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+            apply plugin: 'java'
+
+            group = 'group'
+            version = '1.0'
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    maven {
+                        pom.withXml {
+                            asNode().foo = 'this is not a real element'
+                        }
+                    }
+                }
+            }
+        """
+        when:
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':publishMavenPublicationToMavenRepository'")
+        failure.assertHasCause("Could not apply withXml() to generated POM")
+        failure.assertHasCause("No such property: foo for class: groovy.util.Node")
+    }
+}
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/SamplesMavenPublishIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/SamplesMavenPublishIntegrationTest.groovy
new file mode 100644
index 0000000..a0c678b
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/SamplesMavenPublishIntegrationTest.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.api.publish.maven
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.Sample
+import org.junit.Rule
+
+public class SamplesMavenPublishIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule Sample sample = new Sample("maven/publish-new")
+
+    def sample() {
+        given:
+        executer.inDirectory(sample.dir)
+
+        and:
+        def fileRepo = maven(sample.dir.file("build/repo"))
+        def module = fileRepo.module("org.gradle.sample", "publish-new", "1.0")
+
+        when:
+        succeeds "publish"
+
+        then:
+        def pom = module.parsedPom
+        module.assertPublishedAsJavaModule()
+        pom.description == "A demonstration of maven pom customisation"
+        pom.scopes.runtime.assertDependsOn("commons-collections", "commons-collections", "3.0")
+    }
+}
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPluginIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPluginIntegTest.groovy
new file mode 100644
index 0000000..7007690
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPluginIntegTest.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.plugins
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class MavenPublishPluginIntegTest extends WellBehavedPluginTest {
+
+    @Override
+    String getPluginId() {
+        "maven-publish"
+    }
+
+    @Override
+    String getMainTask() {
+        "publish"
+    }
+
+}
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/enforcerplugin/pom.xml b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/enforcerplugin/pom.xml
new file mode 100644
index 0000000..7791c22
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/enforcerplugin/pom.xml
@@ -0,0 +1,42 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>util</groupId>
+    <artifactId>enforcerExample</artifactId>
+    <version>1.0</version>
+    <packaging>jar</packaging>
+    <dependencies>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.6</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-enforcer-plugin</artifactId>
+                <version>1.1.1</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>enforce</goal>
+                        </goals>
+                        <inherited>true</inherited>
+                        <configuration>
+                            <rules>
+                                <bannedDependencies>
+                                    <excludes>
+                                        <exclude>org.apache.maven</exclude>
+                                        <exclude>org.apache.maven:badArtifact</exclude>
+                                        <exclude>*:badArtifact</exclude>
+                                    </excludes>
+                                </bannedDependencies>
+                            </rules>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/enforcerplugin/src/main/java/Foo.java b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/enforcerplugin/src/main/java/Foo.java
new file mode 100644
index 0000000..569228a
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/enforcerplugin/src/main/java/Foo.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.commons.lang.StringUtils;
+
+public class Foo {
+  public String toString() {
+    return StringUtils.normalizeSpace("hi  there!");
+  }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenPom.java
index 4b94173..95e4829 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenPom.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenPom.java
@@ -30,6 +30,9 @@ import java.util.List;
  * @author Hans Dockter
  */
 public interface MavenPom {
+
+    String POM_FILE_ENCODING = "UTF-8";
+
     /**
      * Returns the scope mappings used for generating this pom.
      */
@@ -152,7 +155,8 @@ public interface MavenPom {
     MavenPom setModel(Object model);
 
     /**
-     * Writes the {@link #getEffectivePom()} xml to a writer while applying the {@link #withXml(org.gradle.api.Action)} actions.
+     * Writes the {@link #getEffectivePom()} xml to a writer while applying the {@link #withXml(org.gradle.api.Action)} actions. Closes the supplied
+     * Writer when finished.
      *
      * @param writer The writer to write the pom xml.
      * @return this
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPlugin.java b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPlugin.java
index bc42495..6fdaee1 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPlugin.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPlugin.java
@@ -24,6 +24,7 @@ import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
 import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler;
+import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.publication.maven.internal.DefaultDeployerFactory;
@@ -54,10 +55,12 @@ public class MavenPlugin implements Plugin<ProjectInternal> {
     public static final String INSTALL_TASK_NAME = "install";
 
     private final Factory<LoggingManagerInternal> loggingManagerFactory;
+    private final FileResolver fileResolver;
 
     @Inject
-    public MavenPlugin(Factory<LoggingManagerInternal> loggingManagerFactory) {
+    public MavenPlugin(Factory<LoggingManagerInternal> loggingManagerFactory, FileResolver fileResolver) {
         this.loggingManagerFactory = loggingManagerFactory;
+        this.fileResolver = fileResolver;
     }
 
     public void apply(final ProjectInternal project) {
@@ -68,7 +71,7 @@ public class MavenPlugin implements Plugin<ProjectInternal> {
         final DefaultDeployerFactory deployerFactory = new DefaultDeployerFactory(
                 mavenFactory,
                 loggingManagerFactory,
-                project.getFileResolver(),
+                fileResolver,
                 pluginConvention,
                 project.getConfigurations(),
                 pluginConvention.getConf2ScopeMappings());
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/Maven2Gradle.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/Maven2Gradle.groovy
index 7e212db..7c67fe2 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/Maven2Gradle.groovy
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/Maven2Gradle.groovy
@@ -211,7 +211,7 @@ ${globalExclusions(this.effectivePom)}
     def enforcerPlugin = plugin('maven-enforcer-plugin', project)
     def enforceGoal = pluginGoal('enforce', enforcerPlugin)
     if (enforceGoal) {
-      exclusions += 'configurations.allObjects {\n'
+      exclusions += 'configurations.all {\n'
       enforceGoal.configuration.rules.bannedDependencies.excludes.childNodes().each {
         def tokens = it.text().tokenize(':')
         exclusions += "it.exclude group: '${tokens[0]}'"
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/CustomTaskFactoryDeployerFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/CustomTaskFactoryDeployerFactory.java
new file mode 100644
index 0000000..19c2362
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/CustomTaskFactoryDeployerFactory.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publication.maven.internal;
+
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.publication.maven.internal.ant.CustomDeployTask;
+import org.gradle.api.publication.maven.internal.ant.DefaultGroovyMavenDeployer;
+import org.gradle.internal.Factory;
+import org.gradle.logging.LoggingManagerInternal;
+
+public class CustomTaskFactoryDeployerFactory extends DefaultDeployerFactory {
+    private final Factory<CustomDeployTask> taskFactory;
+
+    public CustomTaskFactoryDeployerFactory(MavenFactory mavenFactory, Factory<LoggingManagerInternal> loggingManagerFactory, FileResolver fileResolver, MavenPomMetaInfoProvider pomMetaInfoProvider, ConfigurationContainer configurationContainer, Conf2ScopeMappingContainer scopeMapping,
+                                            Factory<CustomDeployTask> taskFactory) {
+        super(mavenFactory, loggingManagerFactory, fileResolver, pomMetaInfoProvider, configurationContainer, scopeMapping);
+        this.taskFactory = taskFactory;
+    }
+
+    @Override
+    public DefaultGroovyMavenDeployer createMavenDeployer() {
+        DefaultGroovyMavenDeployer mavenDeployer = super.createMavenDeployer();
+        mavenDeployer.setDeployTaskFactory(taskFactory);
+        return mavenDeployer;
+
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultDeployerFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultDeployerFactory.java
index d3ad057..92d3737 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultDeployerFactory.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultDeployerFactory.java
@@ -16,11 +16,14 @@
 package org.gradle.api.publication.maven.internal;
 
 import org.gradle.api.artifacts.ConfigurationContainer;
-import org.gradle.api.artifacts.maven.*;
-import org.gradle.internal.Factory;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.artifacts.maven.MavenResolver;
+import org.gradle.api.artifacts.maven.PomFilterContainer;
+import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.publication.maven.internal.ant.BaseMavenInstaller;
 import org.gradle.api.publication.maven.internal.ant.DefaultGroovyMavenDeployer;
-import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.Factory;
 import org.gradle.logging.LoggingManagerInternal;
 
 public class DefaultDeployerFactory implements DeployerFactory {
@@ -31,7 +34,8 @@ public class DefaultDeployerFactory implements DeployerFactory {
     private final ConfigurationContainer configurationContainer;
     private final Conf2ScopeMappingContainer scopeMapping;
 
-    public DefaultDeployerFactory(MavenFactory mavenFactory, Factory<LoggingManagerInternal> loggingManagerFactory, FileResolver fileResolver, MavenPomMetaInfoProvider pomMetaInfoProvider, ConfigurationContainer configurationContainer, Conf2ScopeMappingContainer scopeMapping) {
+    public DefaultDeployerFactory(MavenFactory mavenFactory, Factory<LoggingManagerInternal> loggingManagerFactory, FileResolver fileResolver, MavenPomMetaInfoProvider pomMetaInfoProvider,
+                                  ConfigurationContainer configurationContainer, Conf2ScopeMappingContainer scopeMapping) {
         this.mavenFactory = mavenFactory;
         this.loggingManagerFactory = loggingManagerFactory;
         this.fileResolver = fileResolver;
@@ -40,7 +44,7 @@ public class DefaultDeployerFactory implements DeployerFactory {
         this.scopeMapping = scopeMapping;
     }
 
-    public GroovyMavenDeployer createMavenDeployer() {
+    public DefaultGroovyMavenDeployer createMavenDeployer() {
         PomFilterContainer pomFilterContainer = createPomFilterContainer(
                 mavenFactory.createMavenPomFactory(configurationContainer, scopeMapping, fileResolver));
         return new DefaultGroovyMavenDeployer(pomFilterContainer, createArtifactPomContainer(
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPom.java
index e202392..fb2d050 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPom.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPom.java
@@ -16,19 +16,20 @@
 package org.gradle.api.publication.maven.internal;
 
 import groovy.lang.Closure;
-import org.apache.commons.io.IOUtils;
 import org.apache.maven.model.Dependency;
 import org.apache.maven.model.Model;
 import org.apache.maven.project.MavenProject;
 import org.codehaus.groovy.runtime.InvokerHelper;
 import org.gradle.api.Action;
+import org.gradle.api.UncheckedIOException;
 import org.gradle.api.XmlProvider;
 import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
 import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.internal.ClosureBackedAction;
 import org.gradle.api.internal.ErroringAction;
 import org.gradle.api.internal.IoActions;
-import org.gradle.api.internal.XmlTransformer;
+import org.gradle.api.internal.xml.XmlTransformer;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.listener.ActionBroadcast;
 
@@ -42,6 +43,7 @@ import java.util.List;
  * @author Hans Dockter
  */
 public class DefaultMavenPom implements MavenPom {
+
     private PomDependenciesConverter pomDependenciesConverter;
     private FileResolver fileResolver;
     private MavenProject mavenProject = new MavenProject();
@@ -186,12 +188,16 @@ public class DefaultMavenPom implements MavenPom {
     }
 
     public DefaultMavenPom writeTo(final Writer pomWriter) {
-        getEffectivePom().writeNonEffectivePom(pomWriter);
+        try {
+            getEffectivePom().writeNonEffectivePom(pomWriter);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
         return this;
     }
 
     public DefaultMavenPom writeTo(Object path) {
-        IoActions.writeFile(fileResolver.resolve(path), new Action<BufferedWriter>() {
+        IoActions.writeFile(fileResolver.resolve(path), POM_FILE_ENCODING, new Action<BufferedWriter>() {
             public void execute(BufferedWriter writer) {
                 writeTo(writer);
             }
@@ -199,20 +205,20 @@ public class DefaultMavenPom implements MavenPom {
         return this;
     }
 
-    private void writeNonEffectivePom(final Writer pomWriter) {
+    private void writeNonEffectivePom(final Writer pomWriter) throws IOException {
         try {
-            withXmlActions.transform(pomWriter, "UTF-8", new ErroringAction<Writer>() {
-                protected void doExecute(Writer writer) throws IOException{
+            withXmlActions.transform(pomWriter, POM_FILE_ENCODING, new ErroringAction<Writer>() {
+                protected void doExecute(Writer writer) throws IOException {
                     mavenProject.writeModel(writer);
                 }
             });
         } finally {
-            IOUtils.closeQuietly(pomWriter);
+            pomWriter.close();
         }
     }
 
     public DefaultMavenPom whenConfigured(final Closure closure) {
-        whenConfiguredActions.add(closure);
+        whenConfiguredActions.add(new ClosureBackedAction<MavenPom>(closure));
         return this;
     }
 
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConvention.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConvention.java
index 3f1e012..f5b12e5 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConvention.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConvention.java
@@ -18,6 +18,9 @@ package org.gradle.api.publication.maven.internal;
 import groovy.lang.Closure;
 import org.gradle.api.artifacts.maven.GroovyMavenDeployer;
 import org.gradle.api.artifacts.maven.MavenResolver;
+import org.gradle.api.internal.Actions;
+import org.gradle.api.internal.ClosureBackedAction;
+import org.gradle.api.internal.ConfigureByMapAction;
 import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler;
 import org.gradle.api.plugins.MavenRepositoryHandlerConvention;
 
@@ -37,15 +40,18 @@ public class DefaultMavenRepositoryHandlerConvention implements MavenRepositoryH
     }
 
     public GroovyMavenDeployer mavenDeployer(Closure configureClosure) {
-        return container.addRepository(createMavenDeployer(), configureClosure, DEFAULT_MAVEN_DEPLOYER_NAME);
+        return container.addRepository(createMavenDeployer(), DEFAULT_MAVEN_DEPLOYER_NAME, new ClosureBackedAction<GroovyMavenDeployer>(configureClosure));
     }
 
     public GroovyMavenDeployer mavenDeployer(Map<String, ?> args) {
-        return container.addRepository(createMavenDeployer(), args, DEFAULT_MAVEN_DEPLOYER_NAME);
+        return container.addRepository(createMavenDeployer(), DEFAULT_MAVEN_DEPLOYER_NAME, new ConfigureByMapAction<GroovyMavenDeployer>(args));
     }
 
     public GroovyMavenDeployer mavenDeployer(Map<String, ?> args, Closure configureClosure) {
-        return container.addRepository(createMavenDeployer(), args, configureClosure, DEFAULT_MAVEN_DEPLOYER_NAME);
+        //noinspection unchecked
+        return container.addRepository(createMavenDeployer(), DEFAULT_MAVEN_DEPLOYER_NAME, Actions.<GroovyMavenDeployer>composite(
+                new ConfigureByMapAction<GroovyMavenDeployer>(args), new ClosureBackedAction<GroovyMavenDeployer>(configureClosure)
+        ));
     }
 
     private GroovyMavenDeployer createMavenDeployer() {
@@ -57,15 +63,18 @@ public class DefaultMavenRepositoryHandlerConvention implements MavenRepositoryH
     }
 
     public MavenResolver mavenInstaller(Closure configureClosure) {
-        return container.addRepository(createMavenInstaller(), configureClosure, DEFAULT_MAVEN_INSTALLER_NAME);
+        return container.addRepository(createMavenInstaller(), DEFAULT_MAVEN_INSTALLER_NAME, new ClosureBackedAction<MavenResolver>(configureClosure));
     }
 
     public MavenResolver mavenInstaller(Map<String, ?> args) {
-        return container.addRepository(createMavenInstaller(), args, DEFAULT_MAVEN_INSTALLER_NAME);
+        return container.addRepository(createMavenInstaller(), DEFAULT_MAVEN_INSTALLER_NAME, new ConfigureByMapAction<MavenResolver>(args));
     }
 
     public MavenResolver mavenInstaller(Map<String, ?> args, Closure configureClosure) {
-        return container.addRepository(createMavenInstaller(), args, configureClosure, DEFAULT_MAVEN_INSTALLER_NAME);
+        //noinspection unchecked
+        return container.addRepository(createMavenInstaller(), DEFAULT_MAVEN_INSTALLER_NAME, Actions.<MavenResolver>composite(
+                new ConfigureByMapAction<MavenResolver>(args), new ClosureBackedAction<MavenResolver>(configureClosure)
+        ));
     }
 
     private MavenResolver createMavenInstaller() {
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolver.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolver.java
index 43de9da..a90d7fd 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolver.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolver.java
@@ -41,6 +41,7 @@ import org.apache.tools.ant.Project;
 import org.gradle.api.Action;
 import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.artifacts.maven.*;
+import org.gradle.api.internal.ClosureBackedAction;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.NoOpRepositoryCacheManager;
 import org.gradle.api.internal.artifacts.repositories.AbstractArtifactRepository;
 import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
@@ -286,7 +287,7 @@ public abstract class AbstractMavenResolver extends AbstractArtifactRepository i
     }
 
     public void beforeDeployment(Closure action) {
-        beforeDeploymentActions.add(action);
+        beforeDeploymentActions.add(new ClosureBackedAction<MavenDeployment>(action));
     }
 
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java
index bd9499d..38435b4 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java
@@ -16,6 +16,9 @@
 package org.gradle.api.publication.maven.internal.ant;
 
 import org.apache.maven.artifact.ant.DeployTask;
+import org.apache.maven.artifact.ant.Pom;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.project.MavenProjectBuilder;
 import org.apache.maven.settings.Settings;
 import org.codehaus.plexus.PlexusContainer;
 
@@ -41,6 +44,15 @@ public class CustomDeployTask extends DeployTask implements CustomInstallDeployT
         super.doExecute();
     }
 
+    @Override
+    public Pom initializePom(ArtifactRepository localArtifactRepository) {
+        // Pom initialization is not thread-safe, as it holds static state in the maven classes.
+        // This class-level lock means this piece of code will never execute concurrently for a given instance of the maven classes
+        synchronized (MavenProjectBuilder.class) {
+            return super.initializePom(localArtifactRepository);
+        }
+    }
+
     public void clearAttachedArtifactsList() {
         attachedArtifacts.clear();
     }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java
index 006a990..735fa8a 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java
@@ -116,8 +116,7 @@ public class DefaultPomDependenciesConverter implements PomDependenciesConverter
         Dependency mavenDependency =  new Dependency();
         mavenDependency.setGroupId(dependency.getGroup());
         if (dependency instanceof ProjectDependency) {
-            String artifactId = new ProjectDependencyArtifactIdExtractorHack((ProjectDependency) dependency).extract();
-            mavenDependency.setArtifactId(artifactId);
+            mavenDependency.setArtifactId(determineProjectDependencyArtifactId((ProjectDependency) dependency));
         } else {
             mavenDependency.setArtifactId(name);
         }
@@ -130,6 +129,10 @@ public class DefaultPomDependenciesConverter implements PomDependenciesConverter
         return mavenDependency;
     }
 
+    protected String determineProjectDependencyArtifactId(ProjectDependency dependency) {
+        return new ProjectDependencyArtifactIdExtractorHack(dependency).extract();
+    }
+
     private List<Exclusion> getExclusions(ModuleDependency dependency, Set<Configuration> configurations) {
         List<Exclusion> mavenExclusions = new ArrayList<Exclusion>();
         Set<ExcludeRule> excludeRules = new HashSet<ExcludeRule>(dependency.getExcludeRules());
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/NoInstallDeployTaskFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/NoInstallDeployTaskFactory.java
new file mode 100644
index 0000000..ddd9deb
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/NoInstallDeployTaskFactory.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publication.maven.internal.ant;
+
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.artifact.repository.DefaultArtifactRepository;
+import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
+import org.gradle.internal.Factory;
+
+import java.io.File;
+
+public class NoInstallDeployTaskFactory implements Factory<CustomDeployTask> {
+    private final Factory<File> temporaryDirFactory;
+
+    public NoInstallDeployTaskFactory(Factory<File> temporaryDirFactory) {
+        this.temporaryDirFactory = temporaryDirFactory;
+    }
+
+    public CustomDeployTask create() {
+        return new NoInstallDeployTask(temporaryDirFactory);
+    }
+
+    private static class NoInstallDeployTask extends CustomDeployTask {
+        private final Factory<File> tmpDirFactory;
+
+        public NoInstallDeployTask(Factory<File> tmpDirFactory) {
+            this.tmpDirFactory = tmpDirFactory;
+        }
+
+        @Override
+        protected ArtifactRepository createLocalArtifactRepository() {
+            ArtifactRepositoryLayout repositoryLayout = (ArtifactRepositoryLayout) lookup(ArtifactRepositoryLayout.ROLE, getLocalRepository().getLayout());
+            return new DefaultArtifactRepository("local", tmpDirFactory.create().toURI().toString(), repositoryLayout);
+        }
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPom.java
new file mode 100644
index 0000000..20a80f4
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPom.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven;
+
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+import org.gradle.api.XmlProvider;
+import org.gradle.api.internal.HasInternalProtocol;
+
+/**
+ * The POM for a Maven publication.
+ *
+ * The {@link #withXml(org.gradle.api.Action)} method can be used to modify the descriptor after it has been generated according to the publication data.
+ *
+ * @since 1.4
+ */
+ at Incubating
+ at HasInternalProtocol
+public interface MavenPom {
+
+    /**
+     * Allows configuration of the POM, after it has been generated according to the input data.
+     *
+     * <pre autoTested="true">
+     * apply plugin: "java"
+     * apply plugin: "maven-publish"
+     *
+     * publishing {
+     *   publications {
+     *     maven.pom.withXml {
+     *       asNode().appendNode('description', 'A demonstration of maven pom customisation')
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * Note that due to Gradle's internal type conversion system, you can pass a Groovy closure to this method and
+     * it will be automatically converted to an {@code Action}.
+     * <p>
+     * Each action/closure passed to this method will be stored as a callback, and executed when the publication
+     * that this descriptor is attached to is published.
+     * <p>
+     * For details on the structure of the XML to be modified, see <a href="http://maven.apache.org/pom.html">the POM reference</a>.
+     *
+     * @param action The configuration action.
+     * @see MavenPublication
+     * @see XmlProvider
+     */
+    void withXml(Action<? super XmlProvider> action);
+
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPublication.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPublication.java
new file mode 100644
index 0000000..ab7e27c
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPublication.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven;
+
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+import org.gradle.api.internal.HasInternalProtocol;
+import org.gradle.api.publish.Publication;
+
+/**
+ * A {@code MavenPublication} is the representation/configuration of how Gradle should publish something in Maven format.
+ *
+ * The "{@code maven-publish}" plugin creates one {@code MavenPublication} named "{@code maven}" in the project's
+ * {@code publishing.publications} container. This publication is configured to publish all of the project's
+ * <i>visible</i> configurations (i.e. {@link org.gradle.api.Project#getConfigurations()}).
+ * <p>
+ * The Maven POM identifying attributes are mapped as follows:
+ * <ul>
+ * <li>{@code groupId} - {@code project.group}</li>
+ * <li>{@code artifactId} - {@code project.name}</li>
+ * <li>{@code version} - {@code project.version}</li>
+ * </ul>
+ * <p>
+ * The ability to add multiple publications and finely configure publications will be added in future Gradle versions.
+ *
+ * <h4>Customising the publication prior to publishing</h4>
+ *
+ * It is possible to modify the generated POM prior to publication. This is done using the {@link MavenPom#withXml(org.gradle.api.Action)} method
+ * of the POM returned via the {@link #getPom()} method, or directly by an action (or closure) passed into {@link #pom(org.gradle.api.Action)}.
+ *
+ *
+ * @since 1.4
+ */
+ at Incubating
+ at HasInternalProtocol
+public interface MavenPublication extends Publication {
+
+    /**
+     * The POM that will be published.
+     *
+     * @return The POM that will be published.
+     */
+    MavenPom getPom();
+
+    /**
+     * Configures the POM that will be published.
+     *
+     * The supplied action will be executed against the {@link #getPom()} result. This method also accepts a closure argument, by type coercion.
+     *
+     * @param configure The configuration action.
+     */
+    void pom(Action<? super MavenPom> configure);
+
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/DefaultMavenPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/DefaultMavenPom.java
new file mode 100644
index 0000000..d06c9c9
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/DefaultMavenPom.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.UserCodeAction;
+import org.gradle.api.XmlProvider;
+import org.gradle.listener.ActionBroadcast;
+
+public class DefaultMavenPom implements MavenPomInternal {
+
+    private final ActionBroadcast<XmlProvider> xmlAction = new ActionBroadcast<XmlProvider>();
+
+    public void withXml(Action<? super XmlProvider> action) {
+        xmlAction.add(new UserCodeAction<XmlProvider>("Could not apply withXml() to generated POM", action));
+    }
+
+    public Action<XmlProvider> getXmlAction() {
+        return xmlAction;
+    }
+
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/DefaultMavenPublication.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/DefaultMavenPublication.java
new file mode 100644
index 0000000..faf0fb7
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/DefaultMavenPublication.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.component.SoftwareComponentInternal;
+import org.gradle.api.publish.maven.MavenPom;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.io.File;
+
+public class DefaultMavenPublication implements MavenPublicationInternal {
+
+    private final String name;
+    private final MavenPomInternal pom;
+    private final MavenProjectIdentity projectIdentity;
+    private final SoftwareComponentInternal component;
+    private final File pomDir;
+
+    public DefaultMavenPublication(
+            String name, Instantiator instantiator, MavenProjectIdentity projectIdentity, SoftwareComponentInternal component, File pomDir
+    ) {
+        this.name = name;
+        this.pom = instantiator.newInstance(DefaultMavenPom.class);
+        this.projectIdentity = projectIdentity;
+        this.component = component;
+        this.pomDir = pomDir;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public MavenPomInternal getPom() {
+        return pom;
+    }
+
+    public void pom(Action<? super MavenPom> configure) {
+        configure.execute(pom);
+    }
+
+    public FileCollection getPublishableFiles() {
+        return component.getArtifacts().getFiles();
+    }
+
+    public MavenNormalizedPublication asNormalisedPublication() {
+        return new MavenNormalizedPublication(projectIdentity, component.getArtifacts(), component.getRuntimeDependencies(), pom.getXmlAction());
+    }
+
+    public File getPomDir() {
+        return pomDir;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenDeployerConfigurer.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenDeployerConfigurer.java
new file mode 100644
index 0000000..1b2ed44
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenDeployerConfigurer.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal;
+
+import org.apache.maven.artifact.ant.Authentication;
+import org.apache.maven.artifact.ant.RemoteRepository;
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.maven.MavenDeployer;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.artifacts.repositories.PasswordCredentials;
+
+public class MavenDeployerConfigurer implements Action<MavenDeployer> {
+
+    private final MavenArtifactRepository artifactRepository;
+
+    public MavenDeployerConfigurer(MavenArtifactRepository artifactRepository) {
+        this.artifactRepository = artifactRepository;
+    }
+
+    public void execute(MavenDeployer deployer) {
+        deployer.setRepository(createRepository());
+    }
+
+    private RemoteRepository createRepository() {
+        RemoteRepository remoteRepository = new RemoteRepository();
+        remoteRepository.setUrl(artifactRepository.getUrl().toString());
+
+        PasswordCredentials credentials = artifactRepository.getCredentials();
+        String username = credentials.getUsername();
+        String password = credentials.getPassword();
+
+        if (username != null || password != null) {
+            Authentication authentication = new Authentication();
+            authentication.setUserName(username);
+            authentication.setPassword(password);
+            remoteRepository.addAuthentication(authentication);
+        }
+
+        return remoteRepository;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenNormalizedPublication.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenNormalizedPublication.java
new file mode 100644
index 0000000..bc816be
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenNormalizedPublication.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.XmlProvider;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.PublishArtifact;
+
+public class MavenNormalizedPublication implements MavenProjectIdentity {
+
+    private final MavenProjectIdentity projectIdentity;
+    private final Iterable<PublishArtifact> artifacts;
+    private final Iterable<Dependency> runtimeDependencies;
+    private final Action<XmlProvider> pomWithXmlAction;
+
+    public MavenNormalizedPublication(MavenProjectIdentity projectIdentity, Iterable<PublishArtifact> artifacts, Iterable<Dependency> runtimeDependencies, Action<XmlProvider> pomWithXmlAction) {
+        this.projectIdentity = projectIdentity;
+        this.artifacts = artifacts;
+        this.runtimeDependencies = runtimeDependencies;
+        this.pomWithXmlAction = pomWithXmlAction;
+    }
+
+    public String getArtifactId() {
+        return projectIdentity.getArtifactId();
+    }
+
+    public String getGroupId() {
+        return projectIdentity.getGroupId();
+    }
+
+    public String getVersion() {
+        return projectIdentity.getVersion();
+    }
+
+    public String getPackaging() {
+        return projectIdentity.getPackaging();
+    }
+
+    public Iterable<PublishArtifact> getArtifacts() {
+        return artifacts;
+    }
+
+    public Iterable<Dependency> getRuntimeDependencies() {
+        return runtimeDependencies;
+    }
+
+    public Action<XmlProvider> getPomWithXmlAction() {
+        return pomWithXmlAction;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPomInternal.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPomInternal.java
new file mode 100644
index 0000000..896f95c
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPomInternal.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.XmlProvider;
+import org.gradle.api.publish.maven.MavenPom;
+
+public interface MavenPomInternal extends MavenPom {
+
+    Action<XmlProvider> getXmlAction();
+
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenProjectIdentity.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenProjectIdentity.java
new file mode 100644
index 0000000..580bdfb
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenProjectIdentity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal;
+
+public interface MavenProjectIdentity {
+
+    String getArtifactId();
+
+    String getGroupId();
+
+    String getVersion();
+
+    String getPackaging();
+
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenProjectIdentityModuleAdapter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenProjectIdentityModuleAdapter.java
new file mode 100644
index 0000000..cb71b84
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenProjectIdentityModuleAdapter.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal;
+
+import org.gradle.api.artifacts.Module;
+
+public class MavenProjectIdentityModuleAdapter implements Module {
+
+    private final MavenProjectIdentity mavenProjectIdentity;
+
+    public MavenProjectIdentityModuleAdapter(MavenProjectIdentity mavenProjectIdentity) {
+        this.mavenProjectIdentity = mavenProjectIdentity;
+    }
+
+    public String getGroup() {
+        return mavenProjectIdentity.getGroupId();
+    }
+
+    public String getName() {
+        return mavenProjectIdentity.getArtifactId();
+    }
+
+    public String getVersion() {
+        return mavenProjectIdentity.getVersion();
+    }
+
+    public String getStatus() {
+        return "integration"; // not used
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublicationInternal.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublicationInternal.java
new file mode 100644
index 0000000..7c3912b
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublicationInternal.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.publish.maven.MavenPublication;
+
+import java.io.File;
+
+public interface MavenPublicationInternal extends MavenPublication {
+
+    MavenPomInternal getPom();
+
+    FileCollection getPublishableFiles();
+
+    MavenNormalizedPublication asNormalisedPublication();
+
+    File getPomDir();
+}
+
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublishDynamicTaskCreator.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublishDynamicTaskCreator.java
new file mode 100644
index 0000000..52a91b3
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublishDynamicTaskCreator.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectList;
+import org.gradle.api.NamedDomainObjectSet;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.ArtifactRepositoryContainer;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.publish.PublicationContainer;
+import org.gradle.api.publish.maven.tasks.PublishToMavenRepository;
+import org.gradle.api.tasks.TaskContainer;
+
+import static org.apache.commons.lang.StringUtils.capitalize;
+
+public class MavenPublishDynamicTaskCreator {
+
+    final private TaskContainer tasks;
+    private final Task publishLifecycleTask;
+
+    public MavenPublishDynamicTaskCreator(TaskContainer tasks, Task publishLifecycleTask) {
+        this.tasks = tasks;
+        this.publishLifecycleTask = publishLifecycleTask;
+    }
+
+    public void monitor(final PublicationContainer publications, final ArtifactRepositoryContainer repositories) {
+        final NamedDomainObjectSet<MavenPublicationInternal> mavenPublications = publications.withType(MavenPublicationInternal.class);
+        final NamedDomainObjectList<MavenArtifactRepository> mavenRepositories = repositories.withType(MavenArtifactRepository.class);
+
+        mavenPublications.all(new Action<MavenPublicationInternal>() {
+            public void execute(MavenPublicationInternal publication) {
+                for (MavenArtifactRepository repository : mavenRepositories) {
+                    maybeCreatePublishTask(publication, repository);
+                }
+            }
+        });
+
+        mavenRepositories.all(new Action<MavenArtifactRepository>() {
+            public void execute(MavenArtifactRepository repository) {
+                for (MavenPublicationInternal publication : mavenPublications) {
+                    maybeCreatePublishTask(publication, repository);
+                }
+            }
+        });
+
+        // Note: we aren't supporting removal of repositories or publications
+        // Note: we also aren't considering that repos have a setName, so their name can change
+        //       (though this is a violation of the Named contract)
+    }
+
+    private void maybeCreatePublishTask(MavenPublicationInternal publication, MavenArtifactRepository repository) {
+        String publicationName = publication.getName();
+        String repositoryName = repository.getName();
+
+        String publishTaskName = calculatePublishTaskName(publicationName, repositoryName);
+        if (tasks.findByName(publishTaskName) == null) {
+            PublishToMavenRepository publishTask = tasks.add(publishTaskName, PublishToMavenRepository.class);
+            publishTask.setPublication(publication);
+            publishTask.setRepository(repository);
+            publishTask.setGroup("publishing");
+            publishTask.setDescription(String.format("Publishes Maven publication '%s' to Maven repository '%s'", publicationName, repositoryName));
+
+            publishLifecycleTask.dependsOn(publishTask);
+        }
+    }
+
+    private String calculatePublishTaskName(String publicationName, String repositoryName) {
+        return String.format("publish%sPublicationTo%sRepository", capitalize(publicationName), capitalize(repositoryName));
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublishLocalDynamicTaskCreator.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublishLocalDynamicTaskCreator.java
new file mode 100644
index 0000000..26aecde
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublishLocalDynamicTaskCreator.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectSet;
+import org.gradle.api.Task;
+import org.gradle.api.publish.PublicationContainer;
+import org.gradle.api.publish.maven.tasks.PublishToMavenLocal;
+import org.gradle.api.tasks.TaskContainer;
+
+import static org.apache.commons.lang.StringUtils.capitalize;
+
+public class MavenPublishLocalDynamicTaskCreator {
+
+    final private TaskContainer tasks;
+    private final Task publishToMavenLocalLifecycleTask;
+
+    public MavenPublishLocalDynamicTaskCreator(TaskContainer tasks, Task publishToMavenLocalLifecycleTask) {
+        this.tasks = tasks;
+        this.publishToMavenLocalLifecycleTask = publishToMavenLocalLifecycleTask;
+    }
+
+    public void monitor(final PublicationContainer publications) {
+        final NamedDomainObjectSet<MavenPublicationInternal> mavenPublications = publications.withType(MavenPublicationInternal.class);
+
+        mavenPublications.all(new Action<MavenPublicationInternal>() {
+            public void execute(MavenPublicationInternal publication) {
+                createInstallTask(publication);
+            }
+        });
+        // Note: we aren't supporting removal of publications
+    }
+
+    private void createInstallTask(MavenPublicationInternal publication) {
+        String publicationName = publication.getName();
+        String installTaskName = calculatePublishLocalTaskName(publicationName);
+
+        PublishToMavenLocal publishTask = tasks.add(installTaskName, PublishToMavenLocal.class);
+        publishTask.setPublication(publication);
+        publishTask.setGroup("publishing");
+        publishTask.setDescription(String.format("Publishes Maven publication '%s' to Maven Local repository", publicationName));
+
+        publishToMavenLocalLifecycleTask.dependsOn(publishTask);
+    }
+
+    private String calculatePublishLocalTaskName(String publicationName) {
+        return String.format("publish%sPublicationToMavenLocal", capitalize(publicationName));
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublisher.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublisher.java
new file mode 100644
index 0000000..7bc7d78
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublisher.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.artifacts.*;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.artifacts.maven.MavenDeployer;
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.internal.Cast;
+import org.gradle.api.internal.artifacts.ArtifactPublisher;
+import org.gradle.api.publication.maven.internal.DefaultConf2ScopeMappingContainer;
+import org.gradle.api.publication.maven.internal.DeployerFactory;
+import org.gradle.api.publication.maven.internal.PomDependenciesConverter;
+import org.gradle.api.publication.maven.internal.ant.DefaultExcludeRuleConverter;
+import org.gradle.api.publication.maven.internal.ant.DefaultPomDependenciesConverter;
+import org.gradle.api.publication.maven.internal.ant.ProjectDependencyArtifactIdExtractorHack;
+import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
+import org.gradle.internal.Factory;
+
+import java.util.Collections;
+import java.util.List;
+
+public class MavenPublisher {
+
+    private final DeployerFactory deployerFactory;
+    private final Factory<Configuration> configurationFactory;
+    private final ArtifactPublisher artifactPublisher;
+    private final PomDependenciesConverter pomDependenciesConverter = new MavenPublishPomDependenciesConverter();
+
+    public MavenPublisher(DeployerFactory deployerFactory, Factory<Configuration> configurationFactory, ArtifactPublisher artifactPublisher) {
+        this.deployerFactory = deployerFactory;
+        this.configurationFactory = configurationFactory;
+        this.artifactPublisher = artifactPublisher;
+    }
+
+    public void publish(MavenNormalizedPublication publication, MavenArtifactRepository artifactRepository) {
+        MavenDeployer deployer = deployerFactory.createMavenDeployer();
+        new MavenDeployerConfigurer(artifactRepository).execute(deployer);
+        doPublish(publication, deployer);
+    }
+
+    private void doPublish(MavenNormalizedPublication publication, MavenDeployer deployer) {
+        Configuration publishConfiguration = createPopulatedConfiguration(publication);
+
+        MavenPom deployerPom = deployer.getPom();
+        copyIdentity(publication, deployerPom);
+        copyDependencies(publishConfiguration, deployerPom);
+        deployerPom.withXml(publication.getPomWithXmlAction());
+
+        Module module = new MavenProjectIdentityModuleAdapter(publication);
+
+        DependencyResolver dependencyResolver = Cast.cast(DependencyResolver.class, deployer);
+        artifactPublisher.publish(Collections.singleton(dependencyResolver), module, Collections.singleton(publishConfiguration), null);
+    }
+
+    private void copyIdentity(MavenProjectIdentity projectIdentity, MavenPom pom) {
+        pom.setArtifactId(projectIdentity.getArtifactId());
+        pom.setGroupId(projectIdentity.getGroupId());
+        pom.setVersion(projectIdentity.getVersion());
+        pom.setPackaging(projectIdentity.getPackaging());
+    }
+
+    private void copyDependencies(Configuration configuration, MavenPom pom) {
+        Conf2ScopeMappingContainer mappingContainer = new DefaultConf2ScopeMappingContainer();
+        mappingContainer.addMapping(0, configuration, "runtime");
+        List<?> mavenDependencies = pomDependenciesConverter.convert(mappingContainer, Collections.singleton(configuration));
+        pom.setDependencies(mavenDependencies);
+    }
+
+    Configuration createPopulatedConfiguration(MavenNormalizedPublication publication) {
+        Configuration configuration = configurationFactory.create();
+        for (PublishArtifact artifact : publication.getArtifacts()) {
+            configuration.getArtifacts().add(artifact);
+        }
+        for (Dependency runtimeDependency : publication.getRuntimeDependencies()) {
+            configuration.getDependencies().add(runtimeDependency);
+        }
+        return configuration;
+    }
+
+    private static class MavenPublishPomDependenciesConverter extends DefaultPomDependenciesConverter {
+        public MavenPublishPomDependenciesConverter() {
+            super(new DefaultExcludeRuleConverter());
+        }
+
+        @Override
+        protected String determineProjectDependencyArtifactId(ProjectDependency dependency) {
+            // Don't use artifact id hacks when the target project has the maven-publish plugin applied
+            if (dependency.getDependencyProject().getPlugins().hasPlugin(MavenPublishPlugin.class)) {
+                return dependency.getDependencyProject().getName();
+            }
+            return new ProjectDependencyArtifactIdExtractorHack(dependency).extract();
+        }
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/ModuleBackedMavenProjectIdentity.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/ModuleBackedMavenProjectIdentity.java
new file mode 100644
index 0000000..3b6473c
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/ModuleBackedMavenProjectIdentity.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal;
+
+import org.gradle.api.artifacts.Module;
+
+public class ModuleBackedMavenProjectIdentity implements MavenProjectIdentity {
+
+    private final Module module;
+
+    public ModuleBackedMavenProjectIdentity(Module module) {
+        this.module = module;
+    }
+
+    public String getArtifactId() {
+        return module.getName();
+    }
+
+    public String getGroupId() {
+        return module.getGroup();
+    }
+
+    public String getVersion() {
+        return module.getVersion();
+    }
+
+    public String getPackaging() {
+        return null;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/package-info.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/package-info.java
new file mode 100644
index 0000000..79ee9f0
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Types that deal with publishing in the Maven format.
+ *
+ * @since 1.4
+ */
+ at Incubating
+package org.gradle.api.publish.maven;
+
+import org.gradle.api.Incubating;
\ No newline at end of file
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPlugin.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPlugin.java
new file mode 100644
index 0000000..a702af7
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPlugin.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.plugins;
+
+import org.gradle.api.*;
+import org.gradle.api.component.SoftwareComponent;
+import org.gradle.api.internal.ConventionMapping;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.component.SoftwareComponentInternal;
+import org.gradle.api.internal.plugins.DslObject;
+import org.gradle.api.publish.PublishingExtension;
+import org.gradle.api.publish.maven.MavenPublication;
+import org.gradle.api.publish.maven.internal.DefaultMavenPublication;
+import org.gradle.api.publish.maven.internal.MavenPublishDynamicTaskCreator;
+import org.gradle.api.publish.maven.internal.MavenPublishLocalDynamicTaskCreator;
+import org.gradle.api.publish.maven.internal.ModuleBackedMavenProjectIdentity;
+import org.gradle.api.publish.plugins.PublishingPlugin;
+import org.gradle.api.tasks.TaskContainer;
+import org.gradle.internal.reflect.Instantiator;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.util.concurrent.Callable;
+
+/**
+ * Adds the ability to publish in the Maven format to Maven repositories.
+ *
+ * @since 1.4
+ */
+ at Incubating
+public class MavenPublishPlugin implements Plugin<Project> {
+
+    public static final String PUBLISH_LOCAL_LIFECYCLE_TASK_NAME = "publishToMavenLocal";
+
+    private final Instantiator instantiator;
+    private final DependencyMetaDataProvider dependencyMetaDataProvider;
+
+    @Inject
+    public MavenPublishPlugin(Instantiator instantiator, DependencyMetaDataProvider dependencyMetaDataProvider) {
+        this.instantiator = instantiator;
+        this.dependencyMetaDataProvider = dependencyMetaDataProvider;
+    }
+
+    public void apply(final Project project) {
+        project.getPlugins().apply(PublishingPlugin.class);
+        final PublishingExtension extension = project.getExtensions().getByType(PublishingExtension.class);
+
+        // Create the default publication for any components
+        project.getComponents().all(new Action<SoftwareComponent>() {
+            public void execute(SoftwareComponent softwareComponent) {
+                if (!extension.getPublications().withType(MavenPublication.class).isEmpty()) {
+                    throw new IllegalStateException("Cannot publish multiple components to Maven : need to fix this before we add another softwareComponent");
+                }
+                extension.getPublications().add(createPublication("maven", project, softwareComponent));
+            }
+        });
+
+        TaskContainer tasks = project.getTasks();
+
+        // Create publish tasks automatically for any Maven publication and repository combinations
+        Task publishLifecycleTask = tasks.getByName(PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME);
+        MavenPublishDynamicTaskCreator publishTaskCreator = new MavenPublishDynamicTaskCreator(tasks, publishLifecycleTask);
+        publishTaskCreator.monitor(extension.getPublications(), extension.getRepositories());
+
+        // Create install tasks automatically for any Maven publication
+        Task publishLocalLifecycleTask = tasks.add(PUBLISH_LOCAL_LIFECYCLE_TASK_NAME);
+        MavenPublishLocalDynamicTaskCreator publishLocalTaskCreator = new MavenPublishLocalDynamicTaskCreator(tasks, publishLocalLifecycleTask);
+        publishLocalTaskCreator.monitor(extension.getPublications());
+    }
+
+    private MavenPublication createPublication(final String name, final Project project, SoftwareComponent component) {
+        SoftwareComponentInternal componentInternal = (SoftwareComponentInternal) component;
+
+        Callable<Object> pomDirCallable = new Callable<Object>() {
+            public Object call() {
+                return new File(project.getBuildDir(), "publications/" + name);
+            }
+        };
+
+        ModuleBackedMavenProjectIdentity projectIdentity = new ModuleBackedMavenProjectIdentity(dependencyMetaDataProvider.getModule());
+
+        DefaultMavenPublication publication = instantiator.newInstance(
+                DefaultMavenPublication.class,
+                name, instantiator, projectIdentity, componentInternal, null
+        );
+
+        ConventionMapping descriptorConventionMapping = new DslObject(publication).getConventionMapping();
+        descriptorConventionMapping.map("pomDir", pomDirCallable);
+
+        return publication;
+    }
+
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/plugins/package-info.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/plugins/package-info.java
new file mode 100644
index 0000000..9e7da5a
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/plugins/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Plugins for publishing in the Maven format.
+ *
+ * @since 1.4
+ */
+ at Incubating
+package org.gradle.api.publish.maven.plugins;
+
+import org.gradle.api.Incubating;
\ No newline at end of file
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenLocal.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenLocal.java
new file mode 100644
index 0000000..ac83b32
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenLocal.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.tasks;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.artifacts.ArtifactRepositoryContainer;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
+import org.gradle.api.internal.artifacts.BaseRepositoryFactory;
+import org.gradle.api.internal.artifacts.DependencyResolutionServices;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.Factory;
+import org.gradle.logging.LoggingManagerInternal;
+
+import javax.inject.Inject;
+
+/**
+ * Publishes a {@link org.gradle.api.publish.maven.MavenPublication} to the Maven Local repository.
+ *
+ * @since 1.4
+ */
+ at Incubating
+public class PublishToMavenLocal extends PublishToMavenRepository {
+    private final BaseRepositoryFactory baseRepositoryFactory;
+
+    @Inject
+    public PublishToMavenLocal(ArtifactPublicationServices publicationServices, Factory<LoggingManagerInternal> loggingManagerFactory,
+                               FileResolver fileResolver, DependencyResolutionServices dependencyResolutionServices) {
+        super(publicationServices, loggingManagerFactory, fileResolver);
+        this.baseRepositoryFactory = dependencyResolutionServices.getBaseRepositoryFactory();
+    }
+
+    @Override
+    public MavenArtifactRepository getRepository() {
+        if (super.getRepository() == null) {
+            // Instantiate the default MavenLocal repository if none has been set explicitly
+            MavenArtifactRepository mavenLocalRepository = baseRepositoryFactory.createMavenLocalRepository();
+            mavenLocalRepository.setName(ArtifactRepositoryContainer.DEFAULT_MAVEN_LOCAL_REPO_NAME);
+            setRepository(mavenLocalRepository);
+        }
+
+        return super.getRepository();
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenRepository.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenRepository.java
new file mode 100644
index 0000000..b9465f1
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenRepository.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.tasks;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Incubating;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.publication.maven.internal.*;
+import org.gradle.api.publication.maven.internal.ant.NoInstallDeployTaskFactory;
+import org.gradle.api.publish.internal.PublishOperation;
+import org.gradle.api.publish.maven.MavenPublication;
+import org.gradle.api.publish.maven.internal.MavenNormalizedPublication;
+import org.gradle.api.publish.maven.internal.MavenPublicationInternal;
+import org.gradle.api.publish.maven.internal.MavenPublisher;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.internal.Factory;
+import org.gradle.logging.LoggingManagerInternal;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.util.concurrent.Callable;
+
+/**
+ * Publishes a {@link org.gradle.api.publish.maven.MavenPublication} to a {@link MavenArtifactRepository}.
+ *
+ * @since 1.4
+ */
+ at Incubating
+public class PublishToMavenRepository extends DefaultTask {
+
+    private MavenPublicationInternal publication;
+    private MavenArtifactRepository repository;
+
+    private final Factory<LoggingManagerInternal> loggingManagerFactory;
+    private final FileResolver fileResolver;
+    private final ArtifactPublicationServices publicationServices;
+
+    @Inject
+    public PublishToMavenRepository(ArtifactPublicationServices publicationServices, Factory<LoggingManagerInternal> loggingManagerFactory, FileResolver fileResolver) {
+        this.loggingManagerFactory = loggingManagerFactory;
+        this.fileResolver = fileResolver;
+        this.publicationServices = publicationServices;
+
+
+        // Allow the publication to participate in incremental build
+        getInputs().files(new Callable<FileCollection>() {
+            public FileCollection call() throws Exception {
+                MavenPublicationInternal publicationInternal = getPublicationInternal();
+                return publicationInternal == null ? null : publicationInternal.getPublishableFiles();
+            }
+        });
+
+        // Should repositories be able to participate in incremental?
+        // At the least, they may be able to express themselves as output files
+        // They *might* have input files and other dependencies as well though
+        // Inputs: The credentials they need may be expressed in a file
+        // Dependencies: Can't think of a case here
+    }
+
+    /**
+     * The publication to be published.
+     *
+     * @return The publication to be published
+     */
+    public MavenPublication getPublication() {
+        return publication;
+    }
+
+    /**
+     * Sets the publication to be published.
+     *
+     * @param publication The publication to be published
+     */
+    public void setPublication(MavenPublication publication) {
+        this.publication = toPublicationInternal(publication);
+    }
+
+    private MavenPublicationInternal getPublicationInternal() {
+        return toPublicationInternal(getPublication());
+    }
+
+    private static MavenPublicationInternal toPublicationInternal(MavenPublication publication) {
+        if (publication == null) {
+            return null;
+        } else if (publication instanceof MavenPublicationInternal) {
+            return (MavenPublicationInternal) publication;
+        } else {
+            throw new InvalidUserDataException(
+                    String.format(
+                            "publication objects must implement the '%s' interface, implementation '%s' does not",
+                            MavenPublicationInternal.class.getName(),
+                            publication.getClass().getName()
+                    )
+            );
+        }
+    }
+
+    /**
+     * The repository to publish to.
+     *
+     * @return The repository to publish to
+     */
+    public MavenArtifactRepository getRepository() {
+        return repository;
+    }
+
+    /**
+     * Sets the repository to publish to.
+     *
+     * @param repository The repository to publish to
+     */
+    public void setRepository(MavenArtifactRepository repository) {
+        this.repository = repository;
+    }
+
+    @TaskAction
+    public void publish() {
+        MavenPublicationInternal publicationInternal = getPublicationInternal();
+        if (publicationInternal == null) {
+            throw new InvalidUserDataException("The 'publication' property is required");
+        }
+
+        MavenArtifactRepository repository = getRepository();
+        if (repository == null) {
+            throw new InvalidUserDataException("The 'repository' property is required");
+        }
+
+        doPublish(publicationInternal, repository);
+    }
+
+    private void doPublish(final MavenPublicationInternal publication, final MavenArtifactRepository repository) {
+        new PublishOperation(publication, repository) {
+            @Override
+            protected void publish() throws Exception {
+                Factory<Configuration> configurationFactory = new Factory<Configuration>() {
+                    public Configuration create() {
+                        return getProject().getConfigurations().detachedConfiguration();
+                    }
+                };
+                MavenPublisher publisher = new MavenPublisher(createDeployerFactory(), configurationFactory, publicationServices.createArtifactPublisher());
+                MavenNormalizedPublication normalizedPublication = publication.asNormalisedPublication();
+                publisher.publish(normalizedPublication, repository);
+            }
+        }.run();
+    }
+
+    private DeployerFactory createDeployerFactory() {
+        return new CustomTaskFactoryDeployerFactory(
+                new DefaultMavenFactory(),
+                loggingManagerFactory,
+                fileResolver,
+                new MavenPomMetaInfoProvider() {
+                    public File getMavenPomDir() {
+                        return publication.getPomDir();
+                    }
+                },
+                getProject().getConfigurations(), // these won't actually be used, but it's the easiest way to get a ConfigurationContainer.
+                new DefaultConf2ScopeMappingContainer(),
+                new NoInstallDeployTaskFactory(getTemporaryDirFactory())
+        );
+    }
+
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/package-info.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/package-info.java
new file mode 100644
index 0000000..458c54c
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Tasks for publishing in the Maven format.
+ *
+ * @since 1.4
+ */
+ at Incubating
+package org.gradle.api.publish.maven.tasks;
+
+import org.gradle.api.Incubating;
\ No newline at end of file
diff --git a/subprojects/maven/src/main/resources/META-INF/gradle-plugins/maven-publish.properties b/subprojects/maven/src/main/resources/META-INF/gradle-plugins/maven-publish.properties
new file mode 100644
index 0000000..fe5fcfc
--- /dev/null
+++ b/subprojects/maven/src/main/resources/META-INF/gradle-plugins/maven-publish.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.api.publish.maven.plugins.MavenPublishPlugin
\ No newline at end of file
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/plugins/MavenPluginTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/plugins/MavenPluginTest.java
index 967ae16..3f745d5 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/plugins/MavenPluginTest.java
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/plugins/MavenPluginTest.java
@@ -24,7 +24,6 @@ import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.internal.project.DefaultProject;
 import org.gradle.api.tasks.Upload;
 import org.gradle.util.HelperUtil;
-import org.hamcrest.Matchers;
 
 import java.io.File;
 import java.util.Set;
@@ -44,7 +43,7 @@ public class MavenPluginTest {
     public void addsConventionToProject() {
         project.getPlugins().apply(MavenPlugin.class);
 
-        assertThat(project.getConvention().getPlugin(MavenPluginConvention.class), Matchers.<MavenPluginConvention>notNullValue());
+        assertThat(project.getConvention().getPlugin(MavenPluginConvention.class), notNullValue());
     }
     
     @org.junit.Test
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/plugins/maven/internal/MavenProjectsCreatorSpec.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/plugins/maven/internal/MavenProjectsCreatorSpec.groovy
index 8553f43..457a6c7 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/plugins/maven/internal/MavenProjectsCreatorSpec.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/plugins/maven/internal/MavenProjectsCreatorSpec.groovy
@@ -16,19 +16,19 @@
 
 package org.gradle.api.plugins.maven.internal
 
-import spock.lang.Specification
-import org.junit.Rule
-import org.gradle.util.TemporaryFolder
+import org.gradle.api.GradleException
 import org.gradle.api.internal.artifacts.mvnsettings.DefaultMavenSettingsProvider
 import org.gradle.api.internal.artifacts.mvnsettings.MavenFileLocations
-import org.gradle.api.GradleException
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
 
 /**
  * by Szczepan Faber, created at: 10/15/12
  */
 class MavenProjectsCreatorSpec extends Specification {
 
-    @Rule TemporaryFolder temp
+    @Rule TestNameTestDirectoryProvider temp
     private settings = new DefaultMavenSettingsProvider({} as MavenFileLocations)
     private creator = new MavenProjectsCreator()
 
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomTest.java
index 906cdf7..b903870 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomTest.java
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomTest.java
@@ -25,7 +25,7 @@ import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
 import org.gradle.api.artifacts.maven.MavenPom;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.util.TemporaryFolder;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -52,7 +52,7 @@ public class DefaultArtifactPomTest {
     private MavenPom testPom;
 
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
     Mockery context = new JUnit4Mockery();
 
@@ -247,7 +247,7 @@ public class DefaultArtifactPomTest {
     public void writePom() {
         final MavenPom mavenPomMock = context.mock(MavenPom.class);
         DefaultArtifactPom artifactPom = new DefaultArtifactPom(mavenPomMock);
-        final File somePomFile = new File(tmpDir.getDir(), "someDir/somePath");
+        final File somePomFile = new File(tmpDir.getTestDirectory(), "someDir/somePath");
         context.checking(new Expectations() {{
             allowing(mavenPomMock).getArtifactId();
             will(returnValue("artifactId"));
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomTest.groovy
index 29ca9b2..9609dcb 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomTest.groovy
@@ -22,20 +22,22 @@ import org.gradle.api.Action
 import org.gradle.api.artifacts.ConfigurationContainer
 import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer
 import org.gradle.api.internal.file.FileResolver
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.TextUtil
 import org.junit.Rule
 import spock.lang.Specification
 
+import static org.gradle.api.artifacts.maven.MavenPom.POM_FILE_ENCODING
+
 class DefaultMavenPomTest extends Specification {
     static final String EXPECTED_PACKAGING = "something";
     static final String EXPECTED_GROUP_ID = "someGroup";
     static final String EXPECTED_ARTIFACT_ID = "artifactId";
-    static final String EXPECTED_VERSION = "version";
+    static final String EXPECTED_VERSION = "v\u00E9rsi\u00F8n"; // note the utf-8 chars
 
     @Rule
-    TemporaryFolder tmpDir = new TemporaryFolder()
+    TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
     Conf2ScopeMappingContainer conf2ScopeMappingContainer = Mock()
     PomDependenciesConverter pomDependenciesConverterStub = Mock()
@@ -170,15 +172,15 @@ class DefaultMavenPomTest extends Specification {
         mavenPom.writeTo('file');
 
         then:
-        pomFile.text == TextUtil.toPlatformLineSeparators('''<?xml version="1.0" encoding="UTF-8"?>
+        pomFile.getText(POM_FILE_ENCODING) == TextUtil.toPlatformLineSeparators("""<?xml version="1.0" encoding="${POM_FILE_ENCODING}"?>
 <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <modelVersion>4.0.0</modelVersion>
-  <groupId>someGroup</groupId>
-  <artifactId>artifactId</artifactId>
-  <version>version</version>
-  <packaging>something</packaging>
+  <groupId>${EXPECTED_GROUP_ID}</groupId>
+  <artifactId>${EXPECTED_ARTIFACT_ID}</artifactId>
+  <version>${EXPECTED_VERSION}</version>
+  <packaging>${EXPECTED_PACKAGING}</packaging>
 </project>
-''')
+""")
     }
 }
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConventionTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConventionTest.groovy
index 648683a..5973ea9 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConventionTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConventionTest.groovy
@@ -15,12 +15,15 @@
  */
 package org.gradle.api.publication.maven.internal
 
+import org.gradle.api.Action
 import org.gradle.api.artifacts.ConfigurationContainer
 import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer
 import org.gradle.api.artifacts.maven.GroovyMavenDeployer
 import org.gradle.api.artifacts.maven.MavenResolver
+import org.gradle.api.internal.Actions
+import org.gradle.api.internal.ClosureBackedAction
+import org.gradle.api.internal.ConfigureByMapAction
 import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler
-
 import org.gradle.api.internal.file.FileResolver
 import spock.lang.Specification
 
@@ -33,6 +36,18 @@ class DefaultMavenRepositoryHandlerConventionTest extends Specification {
     final MavenPomMetaInfoProvider metaInfoProvider = Mock()
     final DefaultMavenRepositoryHandlerConvention convention = new DefaultMavenRepositoryHandlerConvention(container, factory)
 
+    Action byMap(Map m) {
+        new ConfigureByMapAction(m)
+    }
+
+    Action byClosure(Closure c) {
+        new ClosureBackedAction(c)
+    }
+
+    Action composite(Action... actions) {
+        Actions.composite(actions)
+    }
+
     public void mavenDeployerWithoutName() {
         GroovyMavenDeployer deployer = Mock()
 
@@ -54,7 +69,7 @@ class DefaultMavenRepositoryHandlerConventionTest extends Specification {
         then:
         result == deployer
         1 * factory.createMavenDeployer() >> deployer
-        1 * container.addRepository(deployer, [name: 'someName'], "mavenDeployer") >> deployer
+        1 * container.addRepository(deployer, "mavenDeployer", byMap(name: 'someName')) >> deployer
     }
 
     public void mavenDeployerWithArgsAndClosure() {
@@ -69,7 +84,7 @@ class DefaultMavenRepositoryHandlerConventionTest extends Specification {
         then:
         result == deployer
         1 * factory.createMavenDeployer() >> deployer
-        1 * container.addRepository(deployer, [name: 'someName'], cl, "mavenDeployer") >> deployer
+        1 * container.addRepository(deployer, "mavenDeployer", composite(byMap(name: 'someName'), byClosure(cl))) >> deployer
     }
 
     public void mavenDeployerWithClosure() {
@@ -84,7 +99,7 @@ class DefaultMavenRepositoryHandlerConventionTest extends Specification {
         then:
         result == deployer
         1 * factory.createMavenDeployer() >> deployer
-        1 * container.addRepository(deployer, cl, "mavenDeployer") >> deployer
+        1 * container.addRepository(deployer, "mavenDeployer", byClosure(cl)) >> deployer
     }
 
     public void mavenInstallerWithoutName() {
@@ -108,7 +123,7 @@ class DefaultMavenRepositoryHandlerConventionTest extends Specification {
         then:
         result == installer
         1 * factory.createMavenInstaller() >> installer
-        1 * container.addRepository(installer, [name: 'name'], "mavenInstaller") >> installer
+        1 * container.addRepository(installer, "mavenInstaller", byMap(name: 'name')) >> installer
     }
 
     public void mavenInstallerWithNameAndClosure() {
@@ -121,7 +136,7 @@ class DefaultMavenRepositoryHandlerConventionTest extends Specification {
         then:
         result == installer
         1 * factory.createMavenInstaller() >> installer
-        1 * container.addRepository(installer, [name: 'name'], cl, "mavenInstaller") >> installer
+        1 * container.addRepository(installer, "mavenInstaller", composite(byMap(name: 'name'), byClosure(cl))) >> installer
     }
 
     public void mavenInstallerWithClosure() {
@@ -134,7 +149,7 @@ class DefaultMavenRepositoryHandlerConventionTest extends Specification {
         then:
         result == installer
         1 * factory.createMavenInstaller() >> installer
-        1 * container.addRepository(installer, cl, "mavenInstaller") >> installer
+        1 * container.addRepository(installer, "mavenInstaller", byClosure(cl)) >> installer
     }
 
 }
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/ModuleBackedMavenProjectIdentityTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/ModuleBackedMavenProjectIdentityTest.groovy
new file mode 100644
index 0000000..73eb492
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/ModuleBackedMavenProjectIdentityTest.groovy
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal
+
+import org.gradle.api.artifacts.Module
+import spock.lang.Specification
+
+class ModuleBackedMavenProjectIdentityTest extends Specification {
+
+    def "attributes"() {
+        when:
+        def module = new MutableModule(group: "group", name: "name", version: "version", status: "status")
+        def identity = identity(module)
+
+        then:
+        identity.artifactId == "name"
+        identity.groupId == "group"
+        identity.version == "version"
+        identity.packaging == null
+
+        when:
+        module.name = "changed-name"
+        module.group = "changed-group"
+        module.version = "changed-version"
+
+        then:
+        identity.artifactId == "changed-name"
+        identity.groupId == "changed-group"
+        identity.version == "changed-version"
+        identity.packaging == null
+    }
+
+    MavenProjectIdentity identity(Module module) {
+        new ModuleBackedMavenProjectIdentity(module)
+    }
+
+    private static class MutableModule implements Module {
+        String name
+        String group
+        String version
+        String status
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPluginTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPluginTest.groovy
new file mode 100644
index 0000000..f65d759
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPluginTest.groovy
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.plugins
+import org.gradle.api.artifacts.ArtifactRepositoryContainer
+import org.gradle.api.internal.artifacts.DependencyResolutionServices
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.publish.PublishingExtension
+import org.gradle.api.publish.maven.internal.DefaultMavenPublication
+import org.gradle.api.publish.maven.internal.MavenPublicationInternal
+import org.gradle.api.publish.maven.tasks.PublishToMavenLocal
+import org.gradle.api.publish.maven.tasks.PublishToMavenRepository
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class MavenPublishPluginTest extends Specification {
+
+    def project = HelperUtil.createRootProject()
+    PublishingExtension publishing
+
+    def setup() {
+        project.plugins.apply(MavenPublishPlugin)
+        publishing = project.extensions.getByType(PublishingExtension)
+    }
+
+    def "no publication without component"() {
+        expect:
+        publishing.publications.empty
+    }
+
+    def "default publication with java plugin"() {
+        when:
+        javaPluginApplied()
+
+        then:
+        publishing.publications.size() == 1
+        publishing.publications.maven instanceof DefaultMavenPublication
+    }
+
+    def "creates publish tasks"() {
+        when:
+        javaPluginApplied()
+        publishing.repositories { maven { url = "http://foo.com" } }
+
+        then:
+        project.tasks["publishMavenPublicationToMavenRepository"] != null
+        project.tasks["publishMavenPublicationToMavenLocal"] != null
+    }
+
+    def "java publication always has jar artifact"() {
+        when:
+        javaPluginApplied()
+
+        then:
+        mainPublication.publishableFiles.singleFile.name ==~ /.*\.jar/;
+    }
+
+    protected MavenPublicationInternal getMainPublication() {
+        publishing.publications.maven
+    }
+
+    def javaPluginApplied() {
+        project.plugins.apply(JavaPlugin)
+    }
+
+    def "task is created for publishing to mavenLocal"() {
+        given:
+        javaPluginApplied()
+
+        expect:
+        publishLocalTasks.size() == 1
+        publishLocalTasks.first().name == "publishMavenPublicationToMavenLocal"
+        publishLocalTasks.first().repository.name == ArtifactRepositoryContainer.DEFAULT_MAVEN_LOCAL_REPO_NAME
+        publishLocalTasks.first().repository.url == project.getServices().get(DependencyResolutionServices).baseRepositoryFactory.createMavenLocalRepository().url
+    }
+
+    def "can explicitly add mavenLocal as a publishing repository"() {
+        given:
+        javaPluginApplied()
+
+        when:
+        def mavenLocal = publishing.repositories.mavenLocal()
+
+        then:
+        publishTasks.size() == 1
+        publishTasks.first().repository.is(mavenLocal)
+
+        publishLocalTasks.size() == 1
+        publishTasks.first().repository.url == publishLocalTasks.first().repository.url
+    }
+
+    def "tasks are created for compatible publication / repo"() {
+        given:
+        javaPluginApplied()
+
+        expect:
+        publishTasks.size() == 0
+
+        when:
+        def repo1 = publishing.repositories.maven { url "foo" }
+
+        then:
+        publishTasks.size() == 1
+        publishTasks.last().repository.is(repo1)
+        publishTasks.last().name == "publishMavenPublicationToMavenRepository"
+
+        when:
+        publishing.repositories.ivy {}
+
+        then:
+        publishTasks.size() == 1
+
+        when:
+        def repo2 = publishing.repositories.maven { url "foo"; name "other" }
+
+        then:
+        publishTasks.size() == 2
+        publishTasks.last().repository.is(repo2)
+        publishTasks.last().name == "publishMavenPublicationToOtherRepository"
+    }
+
+    List<PublishToMavenLocal> getPublishLocalTasks() {
+        project.tasks.withType(PublishToMavenLocal).sort { it.name }
+    }
+
+    List<PublishToMavenRepository> getPublishTasks() {
+        def allTasks = project.tasks.withType(PublishToMavenRepository).sort { it.name }
+        allTasks.removeAll(publishLocalTasks)
+        return allTasks
+    }
+
+    def "publication identity is live wrt project properties"() {
+        given:
+        javaPluginApplied()
+        project.group = "group"
+        project.version = "version"
+
+        expect:
+        with(mainPublication.asNormalisedPublication()) {
+            groupId == "group"
+            version == "version"
+        }
+
+        when:
+        project.group = "changed-group"
+        project.version = "changed-version"
+
+        then:
+        with(mainPublication.asNormalisedPublication()) {
+            groupId == "changed-group"
+            version == "changed-version"
+        }
+    }
+
+    def "pom dir moves with build dir"() {
+        given:
+        javaPluginApplied()
+
+        expect:
+        mainPublication.pomDir == new File(project.buildDir, "publications/${mainPublication.name}")
+
+        when:
+        project.buildDir = project.file("changed")
+
+        then:
+        mainPublication.pomDir == new File(project.buildDir, "publications/${mainPublication.name}")
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenRepositoryTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenRepositoryTest.groovy
new file mode 100644
index 0000000..0167cfb
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenRepositoryTest.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.tasks
+
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class PublishToMavenRepositoryTest extends Specification {
+
+    def project = HelperUtil.createRootProject()
+
+    def "can instantiate"() {
+        when:
+        project.tasks.add("task", PublishToMavenRepository)
+
+        then:
+        true
+    }
+}
diff --git a/subprojects/messaging/messaging.gradle b/subprojects/messaging/messaging.gradle
index 7ab1617..48123b4 100644
--- a/subprojects/messaging/messaging.gradle
+++ b/subprojects/messaging/messaging.gradle
@@ -3,6 +3,7 @@ dependencies {
     publishCompile project(':baseServices')
     publishCompile libraries.slf4j_api
     publishCompile libraries.guava
+    publishCompile 'com.esotericsoftware.kryo:kryo:2.20'
 }
 
 useTestFixtures()
diff --git a/subprojects/messaging/src/integTest/groovy/org/gradle/messaging/remote/BroadcastMessagingIntegrationTest.groovy b/subprojects/messaging/src/integTest/groovy/org/gradle/messaging/remote/BroadcastMessagingIntegrationTest.groovy
new file mode 100644
index 0000000..2faef51
--- /dev/null
+++ b/subprojects/messaging/src/integTest/groovy/org/gradle/messaging/remote/BroadcastMessagingIntegrationTest.groovy
@@ -0,0 +1,340 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote
+
+import org.gradle.messaging.remote.internal.IncomingBroadcast
+import org.gradle.messaging.remote.internal.MessagingServices
+import org.gradle.messaging.remote.internal.OutgoingBroadcast
+import org.gradle.messaging.remote.internal.inet.SocketInetAddress
+import org.gradle.util.ConcurrentSpecification
+import spock.lang.Ignore
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+ at Ignore
+class BroadcastMessagingIntegrationTest extends ConcurrentSpecification {
+    static final Random RANDOM = new Random()
+    final def testGroup = "test-group-${RANDOM.nextLong()}"
+    final def address = new SocketInetAddress(InetAddress.getByName("233.253.17.122"), 7914)
+
+    def "client can discover and send messages to server"() {
+        TestService incoming = Mock()
+        def server = newServer()
+        def client = newClient()
+        def discovered = startsAsyncAction()
+
+        given:
+        server.addIncoming(incoming)
+
+        when:
+        discovered.started {
+            client.outgoing.doStuff("message")
+        }
+
+        then:
+        1 * incoming.doStuff("message") >> { discovered.done() }
+
+        cleanup:
+        server?.stop()
+        client?.stop()
+    }
+
+    def "multiple clients can discover server"() {
+        TestService incoming = Mock()
+        def server = newServer()
+        def client1 = newClient()
+        def client2 = newClient()
+        def discovered1 = startsAsyncAction()
+        def discovered2 = startsAsyncAction()
+
+        given:
+        server.addIncoming(incoming)
+
+        when:
+        discovered1.started {
+            client1.outgoing.doStuff("client1")
+        }
+        discovered2.started {
+            client2.outgoing.doStuff("client2")
+        }
+
+        then:
+        1 * incoming.doStuff("client1") >> { discovered1.done() }
+        1 * incoming.doStuff("client2") >> { discovered2.done() }
+
+        cleanup:
+        server?.stop()
+        client1?.stop()
+        client2?.stop()
+    }
+
+    @Ignore
+    def "client can broadcast to multiple servers"() {
+        TestService incoming1 = Mock()
+        TestService incoming2 = Mock()
+        def server1 = newServer()
+        def server2 = newServer()
+        def client = newClient()
+        def broadcast = new CountDownLatch(2)
+
+        given:
+        server1.addIncoming(incoming1)
+        server2.addIncoming(incoming2)
+
+        when:
+        start {
+            client.outgoing.doStuff("message")
+            broadcast.await()
+        }
+        finished()
+
+        then:
+        1 * incoming1.doStuff("message") >> { broadcast.countDown() }
+        1 * incoming2.doStuff("message") >> { broadcast.countDown() }
+
+        cleanup:
+        server1?.stop()
+        server2?.stop()
+        client?.stop()
+    }
+
+    def "client stop flushes messages"() {
+        TestService incoming = Mock()
+        def client
+        def server = newServer()
+
+        given:
+        server.addIncoming(incoming)
+
+        when:
+        start {
+            client = newClient()
+            client.outgoing.doStuff("message1")
+            client.outgoing.doStuff("message2")
+            client.outgoing.doStuff("message3")
+            client.stop()
+        }
+        finished()
+
+        then:
+        1 * incoming.doStuff("message1")
+        1 * incoming.doStuff("message2")
+        1 * incoming.doStuff("message3")
+
+        cleanup:
+        client?.stop()
+        server?.stop()
+    }
+
+    def "client can start broadcasting before server started"() {
+        TestService incoming = Mock()
+        def client = newClient()
+        def server
+        def discovered = startsAsyncAction()
+
+        when:
+        discovered.started {
+            client.outgoing.doStuff("message")
+            server = newServer()
+            server.addIncoming(incoming)
+        }
+
+        then:
+        1 * incoming.doStuff("message") >> { discovered.done() }
+
+        cleanup:
+        server?.stop()
+        client?.stop()
+    }
+
+    def "client can start broadcasting after server started"() {
+        TestService incoming = Mock()
+        def client
+        def server = newServer()
+        def discovered = startsAsyncAction()
+
+        given:
+        server.addIncoming(incoming)
+
+        when:
+        discovered.started {
+            client = newClient()
+            client.outgoing.doStuff("message")
+        }
+
+        then:
+        1 * incoming.doStuff("message") >> { discovered.done() }
+
+        cleanup:
+        server?.stop()
+        client?.stop()
+    }
+
+    def "client can discover restarted server"() {
+        TestService incoming1 = Mock()
+        TestService incoming2 = Mock()
+        def server1 = newServer()
+        def server2
+        def client = newClient()
+        def discovered1 = startsAsyncAction()
+        def discovered2 = startsAsyncAction()
+
+        given:
+        server1.addIncoming(incoming1)
+
+        when:
+        discovered1.started {
+            client.outgoing.doStuff("message1")
+        }
+
+        then:
+        1 * incoming1.doStuff("message1") >> { discovered1.done() }
+
+        when:
+        discovered2.started {
+            server1.stop()
+            server2 = newServer()
+            server2.addIncoming(incoming2)
+            client.outgoing.doStuff("message2")
+        }
+
+        then:
+        1 * incoming2.doStuff("message2") >> { discovered2.done() }
+
+        cleanup:
+        client?.stop()
+        server1?.stop()
+        server2?.stop()
+    }
+
+    def "client can stop when no server has been discovered"() {
+        def client = newClient()
+
+        when:
+        start {
+            client.outgoing.doStuff("message1")
+            client.stop()
+        }.completesWithin(6, TimeUnit.SECONDS)
+
+        then:
+        notThrown(RuntimeException)
+
+        cleanup:
+        client?.stop()
+    }
+
+    def "can stop client when it has not sent any messages"() {
+        def client = newClient()
+
+        when:
+        start {
+            client.stop()
+        }
+        finished()
+
+        then:
+        notThrown(RuntimeException)
+
+        cleanup:
+        client?.stop()
+    }
+
+    def "groups are independent"() {
+        def server1 = newServer("${testGroup}-1")
+        def server2 = newServer("${testGroup}-2")
+        def client1 = newClient("${testGroup}-1")
+        def client2 = newClient("${testGroup}-2")
+        def received = new CountDownLatch(2)
+        TestService incoming1 = Mock()
+        TestService incoming2 = Mock()
+
+        given:
+        server1.addIncoming(incoming1)
+        server2.addIncoming(incoming2)
+
+        when:
+        start {
+            client1.outgoing.doStuff("client1")
+            client2.outgoing.doStuff("client2")
+            client1.stop()
+            client2.stop()
+            server1.stop()
+            server2.stop()
+        }
+        finished()
+
+        then:
+        1 * incoming1.doStuff("client1") >> { received.countDown() }
+        1 * incoming2.doStuff("client2") >> { received.countDown() }
+        0 * incoming1._
+        0 * incoming2._
+
+        cleanup:
+        server1?.stop()
+        server2?.stop()
+        client1?.stop()
+        client2?.stop()
+    }
+
+    private Client newClient(String group = testGroup) {
+        return new Client(group)
+    }
+
+    private Server newServer(String group = testGroup) {
+        return new Server(group)
+    }
+
+
+    class Server {
+        final def services
+        final def broadcast
+
+        Server(String group) {
+            services = new MessagingServices(getClass().classLoader, group, address)
+            broadcast = services.get(IncomingBroadcast.class)
+        }
+
+        void addIncoming(TestService value) {
+            broadcast.addIncoming(TestService.class, value)
+        }
+
+        void stop() {
+            services.stop()
+        }
+    }
+
+    class Client {
+        final def services
+        final def lookup
+        final TestService outgoing
+
+        Client(String group) {
+            services = new MessagingServices(getClass().classLoader, group, address)
+            lookup = services.get(OutgoingBroadcast.class)
+            outgoing = lookup.addOutgoing(TestService)
+        }
+
+        void stop() {
+            services.stop()
+        }
+    }
+}
+
+interface TestService {
+    void doStuff(String param)
+}
diff --git a/subprojects/messaging/src/integTest/groovy/org/gradle/messaging/remote/UnicastMessagingIntegrationTest.groovy b/subprojects/messaging/src/integTest/groovy/org/gradle/messaging/remote/UnicastMessagingIntegrationTest.groovy
new file mode 100644
index 0000000..87947a9
--- /dev/null
+++ b/subprojects/messaging/src/integTest/groovy/org/gradle/messaging/remote/UnicastMessagingIntegrationTest.groovy
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote
+
+import org.gradle.api.Action
+import org.gradle.internal.CompositeStoppable
+import org.gradle.internal.concurrent.ExecutorFactory
+import org.gradle.messaging.remote.internal.MessagingServices
+import org.gradle.test.fixtures.concurrent.ConcurrentSpec
+import spock.lang.Timeout
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.locks.Condition
+import java.util.concurrent.locks.Lock
+import java.util.concurrent.locks.ReentrantLock
+
+ at Timeout(60)
+class UnicastMessagingIntegrationTest extends ConcurrentSpec {
+    def "server can send messages to client"() {
+        RemoteService1 service = Mock()
+        def server = new Server()
+        def client = new Client(server.address)
+
+        when:
+        client.addIncoming(service)
+        server.outgoingService1.doStuff("1")
+        server.outgoingService1.doStuff("2")
+        server.outgoingService1.doStuff("3")
+        server.outgoingService1.doStuff("4")
+        thread.blockUntil.received
+        server.stop()
+        client.stop()
+
+        then:
+        1 * service.doStuff("1")
+        1 * service.doStuff("2")
+        1 * service.doStuff("3")
+        1 * service.doStuff("4") >> { instant.received }
+
+        cleanup:
+        client?.stop()
+        server?.stop()
+    }
+
+    def "client can send messages to server"() {
+        RemoteService1 service = Mock()
+        def server = new Server()
+        def client = new Client(server.address)
+
+        when:
+        server.addIncoming(service)
+        client.outgoingService1.doStuff("1")
+        client.outgoingService1.doStuff("2")
+        client.outgoingService1.doStuff("3")
+        client.outgoingService1.doStuff("4")
+        thread.blockUntil.received
+        client.stop()
+        server.stop()
+
+        then:
+        1 * service.doStuff("1")
+        1 * service.doStuff("2")
+        1 * service.doStuff("3")
+        1 * service.doStuff("4") >> { instant.received }
+
+        cleanup:
+        client?.stop()
+        server?.stop()
+    }
+
+    def "server and client can both send same type of message"() {
+        RemoteService1 serverService = Mock()
+        RemoteService1 clientService = Mock()
+        def received = new CountDownLatch(2)
+        def server = new Server()
+        def client = new Client(server.address)
+
+        when:
+        start {
+            server.addIncoming(serverService)
+            server.outgoingService1.doStuff("from server 1")
+            server.outgoingService1.doStuff("from server 2")
+        }
+        start {
+            client.addIncoming(clientService)
+            client.outgoingService1.doStuff("from client 1")
+            client.outgoingService1.doStuff("from client 2")
+        }
+        received.await()
+        client.stop()
+        server.stop()
+
+        then:
+        1 * serverService.doStuff("from client 1")
+        1 * serverService.doStuff("from client 2") >> { received.countDown() }
+        1 * clientService.doStuff("from server 1")
+        1 * clientService.doStuff("from server 2") >> { received.countDown() }
+
+        cleanup:
+        client?.stop()
+        server?.stop()
+    }
+
+    def "client and server can each send different types of messages"() {
+        RemoteService1 serverService = Mock()
+        RemoteService2 clientService = Mock()
+        def done = new CountDownLatch(2)
+        def server = new Server()
+        def client = new Client(server.address)
+
+        when:
+        start {
+            server.addIncoming(serverService)
+            server.outgoingService2.doStuff("server1")
+            server.outgoingService2.doStuff("server2")
+        }
+        start {
+            client.addIncoming(clientService)
+            client.outgoingService1.doStuff("client1")
+            client.outgoingService1.doStuff("client2")
+        }
+        done.await()
+        client.stop()
+        server.stop()
+
+        then:
+        1 * serverService.doStuff("client1")
+        1 * serverService.doStuff("client2") >> { done.countDown() }
+        1 * clientService.doStuff("server1")
+        1 * clientService.doStuff("server2") >> { done.countDown() }
+
+        cleanup:
+        client?.stop()
+        server?.stop()
+    }
+
+    def "client can start sending before server has started"() {
+        RemoteService1 service = Mock()
+        def server = new Server()
+        def client
+
+        when:
+        client = new Client(server.address)
+        client.outgoingService1.doStuff("1")
+        client.outgoingService1.doStuff("2")
+        server.addIncoming(service)
+        thread.blockUntil.received
+        server.stop()
+        client?.stop()
+
+        then:
+        1 * service.doStuff("1")
+        1 * service.doStuff("2") >> { instant.received }
+
+        cleanup:
+        client?.stop()
+        server?.stop()
+    }
+
+    abstract class Participant {
+        private RemoteService1 remoteService1
+        private RemoteService2 remoteService2
+
+        abstract ObjectConnection getConnection()
+
+        void addIncoming(RemoteService1 value) {
+            connection.addIncoming(RemoteService1.class, value)
+        }
+
+        void addIncoming(RemoteService2 value) {
+            connection.addIncoming(RemoteService2.class, value)
+        }
+
+        RemoteService1 getOutgoingService1() {
+            if (remoteService1 == null) {
+                remoteService1 = connection.addOutgoing(RemoteService1)
+            }
+            return remoteService1
+        }
+
+        RemoteService2 getOutgoingService2() {
+            if (remoteService2 == null) {
+                remoteService2 = connection.addOutgoing(RemoteService2)
+            }
+            return remoteService2
+        }
+
+        abstract void stop()
+    }
+
+    class Server extends Participant {
+        private final Lock lock = new ReentrantLock()
+        private final Condition condition = lock.newCondition()
+        private final MessagingServices services = new TestMessagingServices()
+        private ConnectionAcceptor acceptor
+        private ObjectConnection connection
+        final Address address
+
+        Server() {
+            def server = services.get(MessagingServer)
+            acceptor = server.accept({ event ->
+                lock.lock()
+                try {
+                    connection = event.connection
+                    condition.signalAll()
+                } finally {
+                    lock.unlock()
+                }
+            } as Action)
+            address = acceptor.address
+        }
+
+        @Override
+        ObjectConnection getConnection() {
+            lock.lock()
+            try {
+                while (connection == null) {
+                    condition.await()
+                }
+                return connection
+            } finally {
+                lock.unlock()
+            }
+        }
+
+        @Override
+        void stop() {
+            lock.lock()
+            try {
+                CompositeStoppable.stoppable(acceptor, connection).stop()
+            } finally {
+                connection = null
+                acceptor = null
+                lock.unlock()
+            }
+            services.stop()
+        }
+    }
+
+    class Client extends Participant {
+        final ObjectConnection connection
+        final MessagingServices services = new TestMessagingServices()
+
+        Client(Address serverAddress) {
+            def client = services.get(MessagingClient)
+            connection = client.getConnection(serverAddress)
+        }
+
+        @Override
+        void stop() {
+            connection?.stop()
+            services.stop()
+        }
+    }
+
+    class TestMessagingServices extends MessagingServices {
+        TestMessagingServices() {
+            super(TestMessagingServices.classLoader)
+        }
+
+        @Override
+        protected ExecutorFactory createExecutorFactory() {
+            return getExecutorFactory()
+        }
+    }
+}
+
+interface RemoteService1 {
+    def doStuff(String value)
+}
+
+interface RemoteService2 {
+    def doStuff(String value)
+}
diff --git a/subprojects/messaging/src/integTest/groovy/org/gradle/messaging/remote/internal/hub/MessageHubIntegrationTest.groovy b/subprojects/messaging/src/integTest/groovy/org/gradle/messaging/remote/internal/hub/MessageHubIntegrationTest.groovy
new file mode 100644
index 0000000..b0ea54b
--- /dev/null
+++ b/subprojects/messaging/src/integTest/groovy/org/gradle/messaging/remote/internal/hub/MessageHubIntegrationTest.groovy
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub
+
+import org.gradle.api.Action
+import org.gradle.messaging.dispatch.Dispatch
+import org.gradle.messaging.remote.internal.Connection
+import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage
+import org.gradle.test.fixtures.concurrent.ConcurrentSpec
+import spock.lang.Timeout
+
+import java.util.concurrent.BlockingQueue
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.LinkedBlockingQueue
+
+ at Timeout(60)
+class MessageHubIntegrationTest extends ConcurrentSpec {
+    final Action<Throwable> errorHandler = Mock()
+
+    def "can wire two hubs together"() {
+        Dispatch<String> clientHandler = Mock()
+        Dispatch<String> serverHandler = Mock()
+        def server = new Participant()
+        def client = new Participant()
+        client.connectTo(server)
+        server.addHandler("channel", serverHandler)
+        client.addHandler("channel", clientHandler)
+
+        def serverDispatch = server.createOutgoing("channel")
+        def clientDispatch = client.createOutgoing("channel")
+
+        when:
+        serverDispatch.dispatch("message 1")
+        serverDispatch.dispatch("message 2")
+        serverDispatch.dispatch("message 3")
+        serverDispatch.dispatch("message 4")
+        serverDispatch.dispatch("message 5")
+        serverDispatch.dispatch("message 6")
+        serverDispatch.dispatch("message 7")
+        thread.blockUntil.repliesReceived
+        server.stop()
+        client.stop()
+
+        then:
+        1 * clientHandler.dispatch("message 1") >> { clientDispatch.dispatch("[message 1]") }
+        1 * clientHandler.dispatch("message 2") >> { clientDispatch.dispatch("[message 2]") }
+        1 * clientHandler.dispatch("message 3")
+        1 * clientHandler.dispatch("message 4")
+        1 * clientHandler.dispatch("message 5")
+        1 * clientHandler.dispatch("message 6")
+        1 * clientHandler.dispatch("message 7") >> { clientDispatch.dispatch("[message 3]"); }
+        1 * serverHandler.dispatch("[message 1]")
+        1 * serverHandler.dispatch("[message 2]")
+        1 * serverHandler.dispatch("[message 3]") >> { instant.repliesReceived }
+        0 * _._
+    }
+
+    def "can wire three hubs together"() {
+        def replies = new CountDownLatch(8)
+        Dispatch<String> client1Handler = Mock()
+        Dispatch<String> client2Handler = Mock()
+        Dispatch<String> serverHandler = Mock()
+        def server = new Participant()
+        def client1 = new Participant()
+        def client2 = new Participant()
+        client1.connectTo(server)
+        client2.connectTo(server)
+        server.addHandler("channel", serverHandler)
+        client1.addHandler("channel", client1Handler)
+        client2.addHandler("channel", client2Handler)
+
+        def serverDispatch = server.createOutgoing("channel")
+        def client1Dispatch = client1.createOutgoing("channel")
+        def client2Dispatch = client2.createOutgoing("channel")
+
+        when:
+        serverDispatch.dispatch("message 1")
+        serverDispatch.dispatch("message 2")
+        serverDispatch.dispatch("message 3")
+        serverDispatch.dispatch("message 4")
+        serverDispatch.dispatch("message 5")
+        serverDispatch.dispatch("message 6")
+        serverDispatch.dispatch("message 7")
+        serverDispatch.dispatch("message 8")
+        replies.await()
+        client1.stop()
+        client2.stop()
+        server.stop()
+
+        then:
+        _ * client1Handler.dispatch(_) >> { String message -> client1Dispatch.dispatch("[${message}]" as String) }
+        _ * client2Handler.dispatch(_) >> { String message -> client2Dispatch.dispatch("[${message}]" as String) }
+        1 * serverHandler.dispatch("[message 1]") >> { replies.countDown() }
+        1 * serverHandler.dispatch("[message 2]") >> { replies.countDown() }
+        1 * serverHandler.dispatch("[message 3]") >> { replies.countDown() }
+        1 * serverHandler.dispatch("[message 4]") >> { replies.countDown() }
+        1 * serverHandler.dispatch("[message 5]") >> { replies.countDown() }
+        1 * serverHandler.dispatch("[message 6]") >> { replies.countDown() }
+        1 * serverHandler.dispatch("[message 7]") >> { replies.countDown() }
+        1 * serverHandler.dispatch("[message 8]") >> { replies.countDown() }
+        0 * _._
+    }
+
+    def "each channel is independent"() {
+        Dispatch<String> clientHandler1 = Mock()
+        Dispatch<String> clientHandler2 = Mock()
+        Dispatch<String> serverHandler = Mock()
+        def server = new Participant()
+        def client = new Participant()
+        client.connectTo(server)
+        server.addHandler("channel", serverHandler)
+        client.addHandler("channel1", clientHandler1)
+        client.addHandler("channel2", clientHandler2)
+
+        def serverDispatch1 = server.createOutgoing("channel1")
+        def serverDispatch2 = server.createOutgoing("channel2")
+        def clientDispatch = client.createOutgoing("channel")
+
+        when:
+        serverDispatch1.dispatch("message 1")
+        serverDispatch1.dispatch("message 2")
+        serverDispatch1.dispatch("message 3")
+        serverDispatch1.dispatch("message 4")
+        serverDispatch2.dispatch("message 5")
+        serverDispatch2.dispatch("message 6")
+        serverDispatch2.dispatch("message 7")
+        serverDispatch2.dispatch("message 8")
+        thread.blockUntil.channel1Done
+        thread.blockUntil.channel2Done
+        server.stop()
+        client.stop()
+
+        then:
+        1 * clientHandler1.dispatch("message 1") >> { clientDispatch.dispatch("[message 1]") }
+        1 * clientHandler1.dispatch("message 2") >> { clientDispatch.dispatch("[message 2]") }
+        1 * clientHandler1.dispatch("message 3")
+        1 * clientHandler1.dispatch("message 4") >> { clientDispatch.dispatch("[message 3]") }
+        1 * clientHandler2.dispatch("message 5")
+        1 * clientHandler2.dispatch("message 6")
+        1 * clientHandler2.dispatch("message 7")
+        1 * clientHandler2.dispatch("message 8") >> { clientDispatch.dispatch("[message 4]"); }
+        1 * serverHandler.dispatch("[message 1]")
+        1 * serverHandler.dispatch("[message 2]")
+        1 * serverHandler.dispatch("[message 3]") >> { instant.channel1Done }
+        1 * serverHandler.dispatch("[message 4]") >> { instant.channel2Done }
+        0 * _._
+    }
+
+    private class Participant {
+        MessageHub hub = new MessageHub("participant", getExecutorFactory(), getErrorHandler())
+
+        Dispatch<String> createOutgoing(String channel) {
+            return hub.getOutgoing(channel, String)
+        }
+
+        void addHandler(String channel, Dispatch<String> handler) {
+            hub.addHandler(channel, handler)
+        }
+
+        def connectTo(Participant other) {
+            def connector = new TestConnector()
+            hub.addConnection(connector.connectionA)
+            other.hub.addConnection(connector.connectionB)
+        }
+
+        def stop() {
+            hub.stop()
+        }
+    }
+
+    private class TestConnector {
+        private final BlockingQueue<InterHubMessage> incomingA = new LinkedBlockingQueue<>()
+        private final BlockingQueue<InterHubMessage> incomingB = new LinkedBlockingQueue<>()
+
+        Connection<InterHubMessage> getConnectionA() {
+            return new Connection<InterHubMessage>() {
+                void dispatch(InterHubMessage message) {
+                    incomingB.put(message)
+                }
+
+                InterHubMessage receive() {
+                    return incomingA.take()
+                }
+
+                void requestStop() {
+                    throw new UnsupportedOperationException()
+                }
+
+                void stop() {
+                    throw new UnsupportedOperationException()
+                }
+            }
+        }
+
+        Connection<InterHubMessage> getConnectionB() {
+            return new Connection<InterHubMessage>() {
+                void dispatch(InterHubMessage message) {
+                    incomingA.put(message)
+                }
+
+                InterHubMessage receive() {
+                    return incomingB.take()
+                }
+
+                void requestStop() {
+                    throw new UnsupportedOperationException()
+                }
+
+                void stop() {
+                    throw new UnsupportedOperationException()
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/MethodInvocation.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/MethodInvocation.java
index 9a185ed..68e51cc 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/MethodInvocation.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/MethodInvocation.java
@@ -19,12 +19,13 @@ import java.lang.reflect.Method;
 import java.util.Arrays;
 
 public class MethodInvocation {
+    private static final Object[] ZERO_ARGS = new Object[0];
     private final Method method;
     private final Object[] arguments;
 
     public MethodInvocation(Method method, Object[] args) {
         this.method = method;
-        arguments = args;
+        arguments = args == null ? ZERO_ARGS : args;
     }
 
     public Object[] getArguments() {
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ConnectionAcceptor.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ConnectionAcceptor.java
new file mode 100644
index 0000000..bd27e4a
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ConnectionAcceptor.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote;
+
+import org.gradle.internal.concurrent.AsyncStoppable;
+
+public interface ConnectionAcceptor extends AsyncStoppable {
+    Address getAddress();
+
+    /**
+     * Stops accepting incoming connections.
+     */
+    void requestStop();
+
+    /**
+     * Stops accepting incoming connections and blocks until the accept action has completed executing for any queued connections.
+     */
+    void stop();
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/MessagingServer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/MessagingServer.java
index c44687e..b4dce65 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/MessagingServer.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/MessagingServer.java
@@ -28,5 +28,5 @@ public interface MessagingServer {
      * @param action The action to execute when the connection has been established.
      * @return The local address of the endpoint, for the peer to connect to.
      */
-    Address accept(Action<ConnectEvent<ObjectConnection>> action);
+    ConnectionAcceptor accept(Action<ConnectEvent<ObjectConnection>> action);
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ObjectConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ObjectConnection.java
index 14c2857..5de98c5 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ObjectConnection.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ObjectConnection.java
@@ -22,7 +22,7 @@ import org.gradle.messaging.dispatch.MethodInvocation;
 /**
  * Manages a set of incoming and outgoing channels between 2 peers. Implementations must be thread-safe.
  */
-public interface ObjectConnection extends Addressable, AsyncStoppable {
+public interface ObjectConnection extends AsyncStoppable {
     /**
      * Creates a transmitter for outgoing messages on the given type. The returned object is thread-safe.
      *
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/CompositeAddress.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/CompositeAddress.java
deleted file mode 100644
index 21007a0..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/CompositeAddress.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.messaging.remote.internal;
-
-import org.gradle.messaging.remote.Address;
-
-public class CompositeAddress implements Address {
-    private final Address address;
-    private final Object qualifier;
-
-    public CompositeAddress(Address address, Object qualifier) {
-        this.address = address;
-        this.qualifier = qualifier;
-    }
-
-    public String getDisplayName() {
-        return String.format("%s:%s", address.getDisplayName(), qualifier);
-    }
-
-    @Override
-    public String toString() {
-        return getDisplayName();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o == this) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        CompositeAddress other = (CompositeAddress) o;
-        return other.address.equals(address) && other.qualifier.equals(qualifier);
-    }
-
-    @Override
-    public int hashCode() {
-        return address.hashCode() ^ qualifier.hashCode();
-    }
-
-    public Address getAddress() {
-        return address;
-    }
-
-    public Object getQualifier() {
-        return qualifier;
-    }
-}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultIncomingBroadcast.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultIncomingBroadcast.java
index 2a95365..3d1acd7 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultIncomingBroadcast.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultIncomingBroadcast.java
@@ -25,6 +25,7 @@ import org.gradle.messaging.dispatch.MethodInvocation;
 import org.gradle.messaging.dispatch.ReflectionDispatch;
 import org.gradle.messaging.remote.Address;
 import org.gradle.messaging.remote.ConnectEvent;
+import org.gradle.messaging.remote.ConnectionAcceptor;
 import org.gradle.messaging.remote.internal.protocol.ChannelAvailable;
 import org.gradle.messaging.remote.internal.protocol.DiscoveryMessage;
 import org.gradle.internal.id.IdGenerator;
@@ -47,8 +48,9 @@ public class DefaultIncomingBroadcast implements IncomingBroadcast, Stoppable {
     private final StoppableExecutor executor;
     private final Address address;
     private final MessageHub hub;
+    private final ConnectionAcceptor acceptor;
 
-    public DefaultIncomingBroadcast(MessageOriginator messageOriginator, String group, AsyncConnection<DiscoveryMessage> connection, IncomingConnector<Message> incomingConnector, ExecutorFactory executorFactory, IdGenerator<UUID> idGenerator, ClassLoader messagingClassLoader) {
+    public DefaultIncomingBroadcast(MessageOriginator messageOriginator, String group, AsyncConnection<DiscoveryMessage> connection, IncomingConnector incomingConnector, ExecutorFactory executorFactory, IdGenerator<UUID> idGenerator, ClassLoader messagingClassLoader) {
         this.messageOriginator = messageOriginator;
         this.group = group;
 
@@ -58,7 +60,8 @@ public class DefaultIncomingBroadcast implements IncomingBroadcast, Stoppable {
         connection.dispatchTo(new GroupMessageFilter(group, protocolStack.getBottom()));
         protocolStack.getBottom().dispatchTo(connection);
 
-        address = incomingConnector.accept(new IncomingConnectionAction(), true);
+        acceptor = incomingConnector.accept(new IncomingConnectionAction(), getClass().getClassLoader(), true);
+        address = acceptor.getAddress();
         hub = new MessageHub("incoming broadcast", messageOriginator.getName(), executorFactory, idGenerator, messagingClassLoader);
 
         LOGGER.info("Created IncomingBroadcast with {}", messageOriginator);
@@ -78,7 +81,7 @@ public class DefaultIncomingBroadcast implements IncomingBroadcast, Stoppable {
     }
 
     public void stop() {
-        CompositeStoppable.stoppable(protocolStack, hub, executor).stop();
+        CompositeStoppable.stoppable(acceptor, protocolStack, hub, executor).stop();
     }
 
     private class IncomingConnectionAction implements Action<ConnectEvent<Connection<Message>>> {
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessageSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessageSerializer.java
index dd17c6f..c2706f7 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessageSerializer.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessageSerializer.java
@@ -15,10 +15,12 @@
  */
 package org.gradle.messaging.remote.internal;
 
-import org.gradle.messaging.remote.internal.inet.InetEndpoint;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.serialize.ObjectReader;
+import org.gradle.messaging.serialize.ObjectWriter;
 
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
 
 public class DefaultMessageSerializer<T> implements MessageSerializer<T> {
     private final ClassLoader classLoader;
@@ -27,11 +29,37 @@ public class DefaultMessageSerializer<T> implements MessageSerializer<T> {
         this.classLoader = classLoader;
     }
 
-    public T read(DataInputStream inputStream, InetEndpoint localAddress, InetEndpoint remoteAddress) throws Exception {
-        return (T) Message.receive(inputStream, classLoader);
+    public ObjectReader<T> newReader(InputStream inputStream, Address localAddress, Address remoteAddress) {
+        return new MessageReader<T>(inputStream, classLoader);
     }
 
-    public void write(T message, DataOutputStream outputStream) throws Exception {
-        Message.send(message, outputStream);
+    public ObjectWriter<T> newWriter(OutputStream outputStream) {
+        return new MessageWriter<T>(outputStream);
+    }
+
+    private static class MessageReader<T> implements ObjectReader<T> {
+        private final InputStream inputStream;
+        private final ClassLoader classLoader;
+
+        public MessageReader(InputStream inputStream, ClassLoader classLoader) {
+            this.inputStream = inputStream;
+            this.classLoader = classLoader;
+        }
+
+        public T read() throws Exception {
+            return (T) Message.receive(inputStream, classLoader);
+        }
+    }
+
+    private static class MessageWriter<T> implements ObjectWriter<T> {
+        private final OutputStream outputStream;
+
+        public MessageWriter(OutputStream outputStream) {
+            this.outputStream = outputStream;
+        }
+
+        public void write(T value) throws Exception {
+            Message.send(value, outputStream);
+        }
     }
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingClient.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingClient.java
deleted file mode 100755
index 7f77b07..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingClient.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.messaging.remote.internal;
-
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.Stoppable;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.MessagingClient;
-import org.gradle.messaging.remote.ObjectConnection;
-
-import java.util.HashSet;
-import java.util.Set;
-
-public class DefaultMessagingClient implements MessagingClient, Stoppable {
-    private final Set<ObjectConnection> connections = new HashSet<ObjectConnection>();
-    private final MultiChannelConnector connector;
-    private final ClassLoader classLoader;
-
-    public DefaultMessagingClient(MultiChannelConnector connector, ClassLoader classLoader) {
-        this.connector = connector;
-        this.classLoader = classLoader;
-    }
-
-    public ObjectConnection getConnection(Address address) {
-        MultiChannelConnection<Object> connection = connector.connect(address);
-        IncomingMethodInvocationHandler incoming = new IncomingMethodInvocationHandler(connection);
-        OutgoingMethodInvocationHandler outgoing = new OutgoingMethodInvocationHandler(connection);
-        ObjectConnection objectConnection = new DefaultObjectConnection(connection, connection, outgoing, incoming);
-        connections.add(objectConnection);
-        return objectConnection;
-    }
-
-    public void stop() {
-        CompositeStoppable.stoppable(connections).stop();
-    }
-}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingServer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingServer.java
deleted file mode 100755
index 5a3e18c..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingServer.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.messaging.remote.internal;
-
-import org.gradle.api.Action;
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.Stoppable;
-import org.gradle.internal.concurrent.AsyncStoppable;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.ConnectEvent;
-import org.gradle.messaging.remote.MessagingServer;
-import org.gradle.messaging.remote.ObjectConnection;
-
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.concurrent.atomic.AtomicReference;
-
-public class DefaultMessagingServer implements MessagingServer, Stoppable {
-    private final MultiChannelConnector connector;
-    private final ClassLoader classLoader;
-    private final Set<ObjectConnection> connections = new CopyOnWriteArraySet<ObjectConnection>();
-
-    public DefaultMessagingServer(MultiChannelConnector connector, ClassLoader classLoader) {
-        this.connector = connector;
-        this.classLoader = classLoader;
-    }
-
-    public Address accept(final Action<ConnectEvent<ObjectConnection>> action) {
-        return connector.accept(new Action<ConnectEvent<MultiChannelConnection<Object>>>() {
-            public void execute(ConnectEvent<MultiChannelConnection<Object>> connectEvent) {
-                finishConnect(connectEvent, action);
-            }
-        });
-    }
-
-    private void finishConnect(ConnectEvent<MultiChannelConnection<Object>> connectEvent,
-                               Action<ConnectEvent<ObjectConnection>> action) {
-        MultiChannelConnection<Object> messageConnection = connectEvent.getConnection();
-        IncomingMethodInvocationHandler incoming = new IncomingMethodInvocationHandler(messageConnection);
-        OutgoingMethodInvocationHandler outgoing = new OutgoingMethodInvocationHandler(messageConnection);
-        AtomicReference<ObjectConnection> connectionRef = new AtomicReference<ObjectConnection>();
-        AsyncStoppable stopControl = new ConnectionAsyncStoppable(messageConnection, connectionRef);
-
-        DefaultObjectConnection connection = new DefaultObjectConnection(messageConnection, stopControl, outgoing, incoming);
-        connectionRef.set(connection);
-        connections.add(connection);
-        action.execute(new ConnectEvent<ObjectConnection>(connection, connectEvent.getLocalAddress(), connectEvent.getRemoteAddress()));
-    }
-
-    public void stop() {
-        for (ObjectConnection connection : connections) {
-            connection.requestStop();
-        }
-        try {
-            CompositeStoppable.stoppable(connections).stop();
-        } finally {
-            connections.clear();
-        }
-    }
-
-    private class ConnectionAsyncStoppable implements AsyncStoppable {
-        private final MultiChannelConnection<Object> messageConnection;
-        private final AtomicReference<ObjectConnection> connectionRef;
-
-        public ConnectionAsyncStoppable(MultiChannelConnection<Object> messageConnection,
-                                        AtomicReference<ObjectConnection> connectionRef) {
-            this.messageConnection = messageConnection;
-            this.connectionRef = connectionRef;
-        }
-
-        public void requestStop() {
-            messageConnection.requestStop();
-        }
-
-        public void stop() {
-            try {
-                messageConnection.stop();
-            } finally {
-                connections.remove(connectionRef.get());
-            }
-        }
-    }
-}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMultiChannelConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMultiChannelConnection.java
deleted file mode 100755
index d768a25..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMultiChannelConnection.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.messaging.remote.internal;
-
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.remote.Address;
-
-class DefaultMultiChannelConnection implements MultiChannelConnection<Object> {
-    private final Address sourceAddress;
-    private final Address destinationAddress;
-    private final MessageHub hub;
-
-    DefaultMultiChannelConnection(MessageHub hub, Connection<Message> connection, Address sourceAddress, Address destinationAddress) {
-        this.hub = hub;
-        this.sourceAddress = sourceAddress;
-        this.destinationAddress = destinationAddress;
-
-        hub.addConnection(connection);
-    }
-
-    public Address getLocalAddress() {
-        if (sourceAddress == null) {
-            throw new UnsupportedOperationException();
-        }
-        return sourceAddress;
-    }
-
-    public Address getRemoteAddress() {
-        if (destinationAddress == null) {
-            throw new UnsupportedOperationException();
-        }
-        return destinationAddress;
-    }
-
-    public void addIncomingChannel(String channelKey, final Dispatch<Object> dispatch) {
-        hub.addIncoming(channelKey, dispatch);
-    }
-
-    public Dispatch<Object> addOutgoingChannel(String channelKey) {
-        return hub.addUnicastOutgoing(channelKey);
-    }
-
-    public void requestStop() {
-        hub.requestStop();
-    }
-
-    public void stop() {
-        requestStop();
-        hub.stop();
-    }
-}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java
deleted file mode 100755
index 73fbd95..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.messaging.remote.internal;
-
-import org.gradle.api.Action;
-import org.gradle.internal.Stoppable;
-import org.gradle.internal.concurrent.ExecutorFactory;
-import org.gradle.internal.concurrent.StoppableExecutor;
-import org.gradle.internal.id.IdGenerator;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.ConnectEvent;
-
-import java.util.UUID;
-
-public class DefaultMultiChannelConnector implements MultiChannelConnector, Stoppable {
-    private final OutgoingConnector<Message> outgoingConnector;
-    private final ExecutorFactory executorFactory;
-    private final StoppableExecutor executorService;
-    private final HandshakeIncomingConnector incomingConnector;
-    private final IdGenerator<UUID> idGenerator;
-    private final ClassLoader messagingClassLoader;
-
-    public DefaultMultiChannelConnector(OutgoingConnector<Message> outgoingConnector, IncomingConnector<Message> incomingConnector,
-                                        ExecutorFactory executorFactory, ClassLoader messagingClassLoader, IdGenerator<UUID> idGenerator) {
-        this.messagingClassLoader = messagingClassLoader;
-        this.idGenerator = idGenerator;
-        this.outgoingConnector = new HandshakeOutgoingConnector(outgoingConnector);
-        this.executorFactory = executorFactory;
-        executorService = executorFactory.create("Incoming Connection Handler");
-        this.incomingConnector = new HandshakeIncomingConnector(incomingConnector, executorService);
-    }
-
-    public void stop() {
-        executorService.stop();
-    }
-
-    public Address accept(final Action<ConnectEvent<MultiChannelConnection<Object>>> action) {
-        Action<ConnectEvent<Connection<Message>>> connectAction = new Action<ConnectEvent<Connection<Message>>>() {
-            public void execute(ConnectEvent<Connection<Message>> event) {
-                finishConnect(event, action);
-            }
-        };
-        return incomingConnector.accept(connectAction, false);
-    }
-
-    private void finishConnect(ConnectEvent<Connection<Message>> event,
-                               Action<ConnectEvent<MultiChannelConnection<Object>>> action) {
-        Address localAddress = event.getLocalAddress();
-        Address remoteAddress = event.getRemoteAddress();
-        MessageHub hub = new MessageHub(String.format("Incoming Connection %s", localAddress), "message server", executorFactory, idGenerator, messagingClassLoader);
-        DefaultMultiChannelConnection channelConnection = new DefaultMultiChannelConnection(hub, event.getConnection(), localAddress, remoteAddress);
-        action.execute(new ConnectEvent<MultiChannelConnection<Object>>(channelConnection, localAddress, remoteAddress));
-    }
-
-    public MultiChannelConnection<Object> connect(Address destinationAddress) {
-        Connection<Message> connection = outgoingConnector.connect(destinationAddress);
-        MessageHub hub = new MessageHub(String.format("Outgoing Connection %s", destinationAddress), "message client", executorFactory, idGenerator, messagingClassLoader);
-        return new DefaultMultiChannelConnection(hub, connection, null, destinationAddress);
-    }
-}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultObjectConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultObjectConnection.java
deleted file mode 100755
index 02f7685..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultObjectConnection.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.messaging.remote.internal;
-
-import org.gradle.internal.concurrent.AsyncStoppable;
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.Addressable;
-import org.gradle.messaging.remote.ObjectConnection;
-
-public class DefaultObjectConnection implements ObjectConnection {
-    private final Addressable addressable;
-    private final AsyncStoppable stopControl;
-    private final OutgoingMethodInvocationHandler outgoing;
-    private final IncomingMethodInvocationHandler incoming;
-
-    public DefaultObjectConnection(Addressable addressable, AsyncStoppable stopControl,
-                                   OutgoingMethodInvocationHandler outgoing, IncomingMethodInvocationHandler incoming) {
-        this.addressable = addressable;
-        this.stopControl = stopControl;
-        this.outgoing = outgoing;
-        this.incoming = incoming;
-    }
-
-    public Address getRemoteAddress() {
-        return addressable.getRemoteAddress();
-    }
-
-    public Address getLocalAddress() {
-        return addressable.getLocalAddress();
-    }
-
-    public <T> void addIncoming(Class<T> type, T instance) {
-        incoming.addIncoming(type, instance);
-    }
-
-    public void addIncoming(Class<?> type, Dispatch<? super MethodInvocation> dispatch) {
-        incoming.addIncoming(type, dispatch);
-    }
-
-    public <T> T addOutgoing(Class<T> type) {
-        return outgoing.addOutgoing(type);
-    }
-
-    public void requestStop() {
-        stopControl.requestStop();
-    }
-
-    public void stop() {
-        stopControl.stop();
-    }
-}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultOutgoingBroadcast.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultOutgoingBroadcast.java
index 2719bd2..9f5f4a0 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultOutgoingBroadcast.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultOutgoingBroadcast.java
@@ -41,7 +41,7 @@ public class DefaultOutgoingBroadcast implements OutgoingBroadcast, Stoppable {
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultOutgoingBroadcast.class);
     private final MessageOriginator messageOriginator;
     private final String group;
-    private final OutgoingConnector<Message> outgoingConnector;
+    private final OutgoingConnector outgoingConnector;
     private final ProtocolStack<DiscoveryMessage> discoveryBroadcast;
     private final Lock lock = new ReentrantLock();
     private final StoppableExecutor executor;
@@ -49,7 +49,7 @@ public class DefaultOutgoingBroadcast implements OutgoingBroadcast, Stoppable {
     private final Set<Address> connections = new HashSet<Address>();
     private final MessageHub hub;
 
-    public DefaultOutgoingBroadcast(MessageOriginator messageOriginator, String group, AsyncConnection<DiscoveryMessage> connection, OutgoingConnector<Message> outgoingConnector, ExecutorFactory executorFactory, final IdGenerator<UUID> idGenerator, ClassLoader messagingClassLoader) {
+    public DefaultOutgoingBroadcast(MessageOriginator messageOriginator, String group, AsyncConnection<DiscoveryMessage> connection, OutgoingConnector outgoingConnector, ExecutorFactory executorFactory, final IdGenerator<UUID> idGenerator, ClassLoader messagingClassLoader) {
         this.messageOriginator = messageOriginator;
         this.group = group;
         this.outgoingConnector = outgoingConnector;
@@ -109,7 +109,7 @@ public class DefaultOutgoingBroadcast implements OutgoingBroadcast, Stoppable {
                     lock.unlock();
                 }
 
-                Connection<Message> syncConnection = outgoingConnector.connect(serviceAddress);
+                Connection<Message> syncConnection = outgoingConnector.connect(serviceAddress, DiscoveryMessage.class.getClassLoader());
                 hub.addConnection(syncConnection);
             }
         }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/HandshakeIncomingConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/HandshakeIncomingConnector.java
deleted file mode 100644
index d760d46..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/HandshakeIncomingConnector.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.messaging.remote.internal;
-
-import org.gradle.api.Action;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.ConnectEvent;
-import org.gradle.messaging.remote.internal.protocol.ConnectRequest;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-public class HandshakeIncomingConnector implements IncomingConnector<Message> {
-    private final IncomingConnector<Message> connector;
-    private final Executor executor;
-    private final Object lock = new Object();
-    private Address localAddress;
-    private long nextId;
-    private final Map<Address, Action<ConnectEvent<Connection<Message>>>> pendingActions = new HashMap<Address, Action<ConnectEvent<Connection<Message>>>>();
-
-    public HandshakeIncomingConnector(IncomingConnector<Message> connector, Executor executor) {
-        this.connector = connector;
-        this.executor = executor;
-    }
-
-    public Address accept(Action<ConnectEvent<Connection<Message>>> action, boolean allowRemote) {
-        assert !allowRemote;
-        synchronized (lock) {
-            if (localAddress == null) {
-                localAddress = connector.accept(handShakeAction(), false);
-            }
-
-            Address localAddress = new CompositeAddress(this.localAddress, nextId++);
-            pendingActions.put(localAddress, action);
-            return localAddress;
-        }
-    }
-
-    private Action<ConnectEvent<Connection<Message>>> handShakeAction() {
-        return new Action<ConnectEvent<Connection<Message>>>() {
-            public void execute(final ConnectEvent<Connection<Message>> connectEvent) {
-                executor.execute(new Runnable() {
-                    public void run() {
-                        handshake(connectEvent);
-                    }
-                });
-            }
-        };
-    }
-
-    private void handshake(ConnectEvent<Connection<Message>> connectEvent) {
-        Connection<Message> connection = connectEvent.getConnection();
-        ConnectRequest request = (ConnectRequest) connection.receive();
-        Address localAddress = request.getDestinationAddress();
-        Action<ConnectEvent<Connection<Message>>> channelConnection;
-        synchronized (lock) {
-            channelConnection = pendingActions.remove(localAddress);
-        }
-        if (channelConnection == null) {
-            throw new IllegalStateException(String.format(
-                    "Request to connect received for unknown address '%s'.", localAddress));
-        }
-        channelConnection.execute(new ConnectEvent<Connection<Message>>(connection, localAddress, connectEvent.getRemoteAddress()));
-    }
-}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/HandshakeOutgoingConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/HandshakeOutgoingConnector.java
deleted file mode 100644
index 57c2c14..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/HandshakeOutgoingConnector.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.messaging.remote.internal;
-
-import org.gradle.internal.UncheckedException;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.internal.protocol.ConnectRequest;
-
-public class HandshakeOutgoingConnector implements OutgoingConnector<Message> {
-    private final OutgoingConnector<Message> connector;
-
-    public HandshakeOutgoingConnector(OutgoingConnector<Message> connector) {
-        this.connector = connector;
-    }
-
-    public Connection<Message> connect(Address destinationAddress) {
-        if (!(destinationAddress instanceof CompositeAddress)) {
-            throw new IllegalArgumentException(String.format("Cannot create a connection to address of unknown type: %s.", destinationAddress));
-        }
-        CompositeAddress compositeAddress = (CompositeAddress) destinationAddress;
-        Address connectionAddress = compositeAddress.getAddress();
-        Connection<Message> connection = connector.connect(connectionAddress);
-        try {
-            connection.dispatch(new ConnectRequest(destinationAddress));
-        } catch (Throwable e) {
-            connection.stop();
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-
-        return connection;
-    }
-}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/IncomingConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/IncomingConnector.java
index 2fbc0d9..9aa69ab 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/IncomingConnector.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/IncomingConnector.java
@@ -17,10 +17,20 @@
 package org.gradle.messaging.remote.internal;
 
 import org.gradle.api.Action;
-import org.gradle.messaging.remote.Address;
 import org.gradle.messaging.remote.ConnectEvent;
+import org.gradle.messaging.remote.ConnectionAcceptor;
+
+public interface IncomingConnector {
+    /**
+     * Allocates a new incoming endpoint. Uses Java serialization.
+     *
+     * @param action the action to execute on incoming connection. The supplied action is not required to be thread-safe.
+     * @param messageClassLoader The ClassLoader to use to deserialize incoming messages.
+     * @param allowRemote If true, only allow connections from remote machines. If false, allow only from the local machine.
+     * @return the address of the endpoint which the connector is listening on.
+     */
+    <T> ConnectionAcceptor accept(Action<ConnectEvent<Connection<T>>> action, ClassLoader messageClassLoader, boolean allowRemote);
 
-public interface IncomingConnector<T> {
     /**
      * Allocates a new incoming endpoint.
      *
@@ -28,5 +38,5 @@ public interface IncomingConnector<T> {
      * @param allowRemote If true, only allow connections from remote machines. If false, allow only from the local machine.
      * @return the address of the endpoint which the connector is listening on.
      */
-    Address accept(Action<ConnectEvent<Connection<T>>> action, boolean allowRemote);
+    <T> ConnectionAcceptor accept(Action<ConnectEvent<Connection<T>>> action, MessageSerializer<T> serializer, boolean allowRemote);
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/IncomingMethodInvocationHandler.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/IncomingMethodInvocationHandler.java
deleted file mode 100755
index fe9a6f1..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/IncomingMethodInvocationHandler.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.messaging.remote.internal;
-
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
-import org.gradle.messaging.dispatch.ReflectionDispatch;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-
-public class IncomingMethodInvocationHandler {
-    private final MultiChannelConnection<Object> connection;
-    private final Set<Class<?>> classes = new CopyOnWriteArraySet<Class<?>>();
-
-    public IncomingMethodInvocationHandler(MultiChannelConnection<Object> connection) {
-        this.connection = connection;
-    }
-
-    public <T> void addIncoming(Class<T> type, T instance) {
-        addIncoming(type, new ReflectionDispatch(instance));
-    }
-
-    public void addIncoming(Class<?> type, Dispatch<? super MethodInvocation> dispatch) {
-        Set<Class<?>> incomingTypes = new HashSet<Class<?>>();
-        addInterfaces(type, incomingTypes);
-        for (Class<?> incomingType : incomingTypes) {
-            if (!classes.add(incomingType)) {
-                throw new IllegalArgumentException(String.format("A handler has already been added for type '%s'.", incomingType.getName()));
-            }
-            connection.addIncomingChannel(incomingType.getName(), new TypeCastDispatch<MethodInvocation, Object>(MethodInvocation.class, dispatch));
-        }
-    }
-
-    private void addInterfaces(Class<?> type, Set<Class<?>> superInterfaces) {
-        superInterfaces.add(type);
-        for (Class<?> superType : type.getInterfaces()) {
-            addInterfaces(superType, superInterfaces);
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Message.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Message.java
index c43e0a4..56ffd0e 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Message.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Message.java
@@ -42,8 +42,10 @@ public abstract class Message implements Serializable {
         private byte[] serializedException;
         private String type;
         private String message;
+        private String toString;
         private ExceptionPlaceholder cause;
         private StackTraceElement[] stackTrace;
+        private RuntimeException toStringRuntimeExec;
 
         public ExceptionPlaceholder(final Throwable throwable) throws IOException {
             ByteArrayOutputStream outstr = new ByteArrayOutputStream();
@@ -70,6 +72,12 @@ public abstract class Message implements Serializable {
 
             type = throwable.getClass().getName();
             message = throwable.getMessage();
+            try {
+                toString = throwable.toString();
+            } catch (RuntimeException toStringRE) {
+                toString = null;
+                toStringRuntimeExec = toStringRE;
+            }
             if (throwable.getCause() != null) {
                 cause = new ExceptionPlaceholder(throwable.getCause());
             }
@@ -110,7 +118,7 @@ public abstract class Message implements Serializable {
             }
 
             if (throwable == null) {
-                throwable = new PlaceholderException(type, message, causeThrowable);
+                throwable = new PlaceholderException(type, message, toString, toStringRuntimeExec, causeThrowable);
                 throwable.setStackTrace(stackTrace);
             }
 
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessageSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessageSerializer.java
index f4e7bb9..74e94ca 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessageSerializer.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessageSerializer.java
@@ -15,13 +15,23 @@
  */
 package org.gradle.messaging.remote.internal;
 
-import org.gradle.messaging.remote.internal.inet.InetEndpoint;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.serialize.ObjectReader;
+import org.gradle.messaging.serialize.ObjectWriter;
 
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
 
 public interface MessageSerializer<T> {
-    T read(DataInputStream inputStream, InetEndpoint localAddress, InetEndpoint remoteAddress) throws Exception;
+    /**
+     * Creates a reader that can deserialize objects from the given input stream. Note that the implementation may perform buffering, and may consume any or all of the
+     * content from the given input stream.
+     */
+    ObjectReader<T> newReader(InputStream inputStream, Address localAddress, Address remoteAddress);
 
-    void write(T message, DataOutputStream outputStream) throws Exception;
+    /**
+     * Creates a writer that can write objects to the given output stream. Note that the implementation must not perform any buffering, so that after calling {@link ObjectWriter#write(Object)}
+     * the serialized object has been flushed to the output stream.
+     */
+    ObjectWriter<T> newWriter(OutputStream outputStream);
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessagingServices.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessagingServices.java
index 9eac745..fe15dfa 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessagingServices.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessagingServices.java
@@ -24,11 +24,18 @@ import org.gradle.internal.id.IdGenerator;
 import org.gradle.internal.id.UUIDGenerator;
 import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.messaging.dispatch.DiscardingFailureHandler;
+import org.gradle.messaging.dispatch.MethodInvocation;
 import org.gradle.messaging.remote.MessagingClient;
 import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.messaging.remote.internal.hub.InterHubMessageSerializer;
+import org.gradle.messaging.remote.internal.hub.MessageHubBackedClient;
+import org.gradle.messaging.remote.internal.hub.MessageHubBackedServer;
+import org.gradle.messaging.remote.internal.hub.MethodInvocationSerializer;
 import org.gradle.messaging.remote.internal.inet.*;
 import org.gradle.messaging.remote.internal.protocol.DiscoveryMessage;
 import org.gradle.messaging.remote.internal.protocol.DiscoveryProtocolSerializer;
+import org.gradle.messaging.serialize.kryo.JavaSerializer;
+import org.gradle.messaging.serialize.kryo.TypeSafeSerializer;
 import org.slf4j.LoggerFactory;
 
 import java.net.InetAddress;
@@ -55,13 +62,12 @@ public class MessagingServices extends DefaultServiceRegistry implements Stoppab
     private final ClassLoader messageClassLoader;
     private final String broadcastGroup;
     private final SocketInetAddress broadcastAddress;
-    private DefaultMessagingClient messagingClient;
-    private DefaultMultiChannelConnector multiChannelConnector;
-    private TcpIncomingConnector<Message> incomingConnector;
+    private MessagingClient messagingClient;
+    private IncomingConnector incomingConnector;
     private DefaultExecutorFactory executorFactory;
-    private DefaultMessagingServer messagingServer;
+    private MessagingServer messagingServer;
     private DefaultIncomingBroadcast incomingBroadcast;
-    private AsyncConnectionAdapter<DiscoveryMessage> multicastConnection;
+    private AsyncConnectionAdapter<DiscoveryMessage> multiCastConnection;
     private DefaultOutgoingBroadcast outgoingBroadcast;
 
     public MessagingServices(ClassLoader messageClassLoader) {
@@ -96,10 +102,9 @@ public class MessagingServices extends DefaultServiceRegistry implements Stoppab
         stoppable.add(incomingConnector);
         stoppable.add(messagingClient);
         stoppable.add(messagingServer);
-        stoppable.add(multiChannelConnector);
         stoppable.add(outgoingBroadcast);
         stoppable.add(incomingBroadcast);
-        stoppable.add(multicastConnection);
+        stoppable.add(multiCastConnection);
         stoppable.add(executorFactory);
         stoppable.stop();
     }
@@ -119,43 +124,42 @@ public class MessagingServices extends DefaultServiceRegistry implements Stoppab
         return new InetAddressFactory();
     }
 
-    protected OutgoingConnector<Message> createOutgoingConnector() {
-        return new TcpOutgoingConnector<Message>(
-                new DefaultMessageSerializer<Message>(
-                        messageClassLoader));
+    protected OutgoingConnector createOutgoingConnector() {
+        return new TcpOutgoingConnector();
     }
 
-    protected IncomingConnector<Message> createIncomingConnector() {
-        incomingConnector = new TcpIncomingConnector<Message>(
+    protected IncomingConnector createIncomingConnector() {
+        incomingConnector = new TcpIncomingConnector(
                 get(ExecutorFactory.class),
-                new DefaultMessageSerializer<Message>(
-                        messageClassLoader),
                 get(InetAddressFactory.class),
-                idGenerator);
+                idGenerator
+        );
         return incomingConnector;
     }
 
-    protected MultiChannelConnector createMultiChannelConnector() {
-        multiChannelConnector = new DefaultMultiChannelConnector(
-                get(OutgoingConnector.class),
-                get(IncomingConnector.class),
-                get(ExecutorFactory.class),
-                messageClassLoader,
-                idGenerator);
-        return multiChannelConnector;
+    protected InterHubMessageSerializer createInterHubSerializer() {
+        return new InterHubMessageSerializer(
+                new TypeSafeSerializer<MethodInvocation>(
+                        MethodInvocation.class,
+                        new MethodInvocationSerializer(
+                                messageClassLoader,
+                                new JavaSerializer<Object[]>(
+                                        messageClassLoader))));
     }
 
     protected MessagingClient createMessagingClient() {
-        messagingClient = new DefaultMessagingClient(
-                get(MultiChannelConnector.class),
-                messageClassLoader);
+        messagingClient = new MessageHubBackedClient(
+                get(OutgoingConnector.class),
+                get(InterHubMessageSerializer.class),
+                get(ExecutorFactory.class));
         return messagingClient;
     }
 
     protected MessagingServer createMessagingServer() {
-        messagingServer = new DefaultMessagingServer(
-                get(MultiChannelConnector.class),
-                messageClassLoader);
+        messagingServer = new MessageHubBackedServer(
+                get(IncomingConnector.class),
+                get(InterHubMessageSerializer.class),
+                get(ExecutorFactory.class));
         return messagingServer;
     }
 
@@ -164,7 +168,7 @@ public class MessagingServices extends DefaultServiceRegistry implements Stoppab
                 get(MessageOriginator.class),
                 broadcastGroup,
                 get(AsyncConnection.class),
-                get(IncomingConnector.class),
+                createIncomingConnector(),
                 get(ExecutorFactory.class),
                 idGenerator,
                 messageClassLoader);
@@ -176,7 +180,7 @@ public class MessagingServices extends DefaultServiceRegistry implements Stoppab
                 get(MessageOriginator.class),
                 broadcastGroup,
                 get(AsyncConnection.class),
-                get(OutgoingConnector.class),
+                createOutgoingConnector(),
                 get(ExecutorFactory.class),
                 idGenerator,
                 messageClassLoader);
@@ -185,10 +189,10 @@ public class MessagingServices extends DefaultServiceRegistry implements Stoppab
 
     protected AsyncConnection<DiscoveryMessage> createMulticastConnection() {
         MulticastConnection<DiscoveryMessage> connection = new MulticastConnection<DiscoveryMessage>(broadcastAddress, new DiscoveryProtocolSerializer());
-        multicastConnection = new AsyncConnectionAdapter<DiscoveryMessage>(
+        multiCastConnection = new AsyncConnectionAdapter<DiscoveryMessage>(
                 connection,
                 new DiscardingFailureHandler<DiscoveryMessage>(LoggerFactory.getLogger(MulticastConnection.class)),
                 get(ExecutorFactory.class));
-        return multicastConnection;
+        return multiCastConnection;
     }
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MultiChannelConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MultiChannelConnection.java
deleted file mode 100755
index 9bad93b..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MultiChannelConnection.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.messaging.remote.internal;
-
-import org.gradle.internal.concurrent.AsyncStoppable;
-import org.gradle.messaging.remote.Addressable;
-import org.gradle.messaging.dispatch.Dispatch;
-
-public interface MultiChannelConnection<T> extends Addressable, AsyncStoppable {
-    /**
-     * Adds a destination for outgoing messages on the given channel. The returned value is thread-safe.
-     */
-    Dispatch<T> addOutgoingChannel(String channelKey);
-
-    /**
-     * Adds a handler for incoming messages on the given channel. The given dispatch is only ever used by a single
-     * thread at any given time.
-     */
-    void addIncomingChannel(String channelKey, Dispatch<T> dispatch);
-
-    /**
-     * Commences graceful stop of this connection. Stops accepting any more outgoing messages, and requests that the
-     * peer stop sending incoming messages.
-     */
-    void requestStop();
-
-    /**
-     * Performs a graceful stop of this connection. Blocks until all dispatched incoming messages have been handled, and
-     * all outgoing messages have been delivered.
-     */
-    void stop();
-}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MultiChannelConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MultiChannelConnector.java
deleted file mode 100755
index 5698229..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MultiChannelConnector.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.messaging.remote.internal;
-
-import org.gradle.api.Action;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.ConnectEvent;
-
-public interface MultiChannelConnector {
-    Address accept(Action<ConnectEvent<MultiChannelConnection<Object>>> action);
-
-    MultiChannelConnection<Object> connect(Address destinationAddress);
-}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingConnector.java
index 2379a6f..f823b6d 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingConnector.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingConnector.java
@@ -17,11 +17,19 @@ package org.gradle.messaging.remote.internal;
 
 import org.gradle.messaging.remote.Address;
 
-public interface OutgoingConnector<T> {
+public interface OutgoingConnector {
     /**
-     * Creates a connection to the given address.
+     * Creates a connection to the given address. Uses default Java serialization for messages.
+     *
+     * @param messageClassLoader ClassLoader to use to load incoming messages.
+     * @throws ConnectException when there is nothing listening on the remote address.
+     */
+    <T> Connection<T> connect(Address destinationAddress, ClassLoader messageClassLoader) throws ConnectException;
+
+    /**
+     * Creates a connection to the given address. Uses the given serializer to convert messages between binary form and objects of type T.
      *
      * @throws ConnectException when there is nothing listening on the remote address
      */
-    Connection<T> connect(Address destinationAddress) throws ConnectException;
+    <T> Connection<T> connect(Address destinationAddress, MessageSerializer<T> serializer) throws ConnectException;
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingMethodInvocationHandler.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingMethodInvocationHandler.java
deleted file mode 100755
index 65068e7..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingMethodInvocationHandler.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.messaging.remote.internal;
-
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.ProxyDispatchAdapter;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-public class OutgoingMethodInvocationHandler {
-    private final Map<Class<?>, ProxyDispatchAdapter<?>> outgoing = new ConcurrentHashMap<Class<?>, ProxyDispatchAdapter<?>>();
-    private final MultiChannelConnection<Object> connection;
-
-    public OutgoingMethodInvocationHandler(MultiChannelConnection<Object> connection) {
-        this.connection = connection;
-    }
-
-    public <T> T addOutgoing(Class<T> type) {
-        ProxyDispatchAdapter<?> existing = outgoing.get(type);
-        if (existing != null) {
-            return type.cast(existing.getSource());
-        }
-
-        Dispatch<Object> dispatch = connection.addOutgoingChannel(type.getName());
-        ProxyDispatchAdapter<T> adapter = new ProxyDispatchAdapter<T>(dispatch, type);
-        outgoing.put(type, adapter);
-        return adapter.getSource();
-    }
-}
\ No newline at end of file
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/PlaceholderException.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/PlaceholderException.java
index de695cf..3ec5c3f 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/PlaceholderException.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/PlaceholderException.java
@@ -20,17 +20,24 @@ package org.gradle.messaging.remote.internal;
  */
 public class PlaceholderException extends RuntimeException {
     private final String exceptionClassName;
-    
-    public PlaceholderException(String exceptionClassName, String message, Throwable cause) {
+    private final String toString;
+    private final RuntimeException toStringRuntimeEx;
+
+    public PlaceholderException(String exceptionClassName, String message, String toString, RuntimeException toStringException, Throwable cause) {
         super(message, cause);
         this.exceptionClassName = exceptionClassName;
+        this.toString = toString;
+        this.toStringRuntimeEx = toStringException;
     }
-    
+
     public String getExceptionClassName() {
         return exceptionClassName;
     }
 
     public String toString() {
-        return String.format("%s: %s", exceptionClassName, getMessage());
+        if(toStringRuntimeEx !=null){
+            throw toStringRuntimeEx;
+        }
+        return toString;
     }
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/ConnectionSet.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/ConnectionSet.java
new file mode 100644
index 0000000..245f750
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/ConnectionSet.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub;
+
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.hub.protocol.ConnectionClosed;
+import org.gradle.messaging.remote.internal.hub.protocol.ConnectionEstablished;
+import org.gradle.messaging.remote.internal.hub.protocol.EndOfStream;
+import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage;
+import org.gradle.messaging.remote.internal.hub.queue.EndPointQueue;
+
+import java.util.HashSet;
+import java.util.Set;
+
+class ConnectionSet {
+    private final Set<ConnectionState> connections = new HashSet<ConnectionState>();
+    private final IncomingQueue incomingQueue;
+    private final OutgoingQueue outgoingQueue;
+    private boolean stopping;
+
+    ConnectionSet(IncomingQueue incomingQueue, OutgoingQueue outgoingQueue) {
+        this.incomingQueue = incomingQueue;
+        this.outgoingQueue = outgoingQueue;
+    }
+
+    public ConnectionState add(Connection<InterHubMessage> connection) {
+        incomingQueue.queue(new ConnectionEstablished(connection));
+        EndPointQueue queue = outgoingQueue.newEndpoint();
+        ConnectionState state = new ConnectionState(this, connection, queue);
+        connections.add(state);
+        return state;
+    }
+
+    public void finished(ConnectionState connectionState) {
+        incomingQueue.queue(new ConnectionClosed(connectionState.getConnection()));
+        connections.remove(connectionState);
+        if (stopping) {
+            maybeStop();
+        }
+    }
+
+    public void requestStop() {
+        stopping = true;
+        maybeStop();
+    }
+
+    private void maybeStop() {
+        if (connections.isEmpty()) {
+            outgoingQueue.discardQueued();
+            incomingQueue.queue(new EndOfStream());
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/ConnectionState.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/ConnectionState.java
new file mode 100644
index 0000000..5071baa
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/ConnectionState.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub;
+
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage;
+import org.gradle.messaging.remote.internal.hub.queue.EndPointQueue;
+
+class ConnectionState {
+    private boolean receiveFinished;
+    private boolean dispatchFinished;
+    private final Connection<InterHubMessage> connection;
+    private final ConnectionSet owner;
+    private final EndPointQueue dispatchQueue;
+
+    ConnectionState(ConnectionSet owner, Connection<InterHubMessage> connection, EndPointQueue dispatchQueue) {
+        this.owner = owner;
+        this.connection = connection;
+        this.dispatchQueue = dispatchQueue;
+    }
+
+    public Connection<InterHubMessage> getConnection() {
+        return connection;
+    }
+
+    public EndPointQueue getDispatchQueue() {
+        return dispatchQueue;
+    }
+
+    public void receiveFinished() {
+        receiveFinished = true;
+        if (!dispatchFinished) {
+            dispatchQueue.stop();
+        }
+        maybeDisconnected();
+    }
+
+    public void dispatchFinished() {
+        dispatchFinished = true;
+        maybeDisconnected();
+    }
+
+    private void maybeDisconnected() {
+        if (dispatchFinished && receiveFinished) {
+            owner.finished(this);
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/HubStateListener.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/HubStateListener.java
new file mode 100644
index 0000000..256079d
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/HubStateListener.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub;
+
+/**
+ * A listener that is notified of changes in the state of a message hub.
+ */
+public interface HubStateListener {
+    /**
+     * Called when a connection is attached to the hub.
+     */
+    void onConnect();
+
+    /**
+     * Called when a connection is detached from the hub.
+     */
+    void onDisconnect();
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/IncomingQueue.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/IncomingQueue.java
new file mode 100644
index 0000000..5e1b8d0
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/IncomingQueue.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub;
+
+import org.gradle.messaging.remote.internal.hub.protocol.EndOfStream;
+import org.gradle.messaging.remote.internal.hub.queue.MultiChannelQueue;
+
+import java.util.concurrent.locks.Lock;
+
+class IncomingQueue extends MultiChannelQueue {
+    IncomingQueue(Lock lock) {
+        super(lock);
+    }
+
+    public void requestStop() {
+        queue(new EndOfStream());
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/InterHubMessageSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/InterHubMessageSerializer.java
new file mode 100644
index 0000000..b97ead5
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/InterHubMessageSerializer.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub;
+
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.internal.MessageSerializer;
+import org.gradle.messaging.remote.internal.hub.protocol.ChannelIdentifier;
+import org.gradle.messaging.remote.internal.hub.protocol.ChannelMessage;
+import org.gradle.messaging.remote.internal.hub.protocol.EndOfStream;
+import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage;
+import org.gradle.messaging.serialize.ObjectReader;
+import org.gradle.messaging.serialize.ObjectWriter;
+import org.gradle.messaging.serialize.kryo.KryoAwareSerializer;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+public class InterHubMessageSerializer implements MessageSerializer<InterHubMessage> {
+    private final KryoAwareSerializer<Object> payloadSerializer;
+
+    public InterHubMessageSerializer(KryoAwareSerializer<Object> payloadSerializer) {
+        this.payloadSerializer = payloadSerializer;
+    }
+
+    public ObjectReader<InterHubMessage> newReader(InputStream inputStream, Address localAddress, Address remoteAddress) {
+        Input input = new Input(inputStream);
+        return new MessageReader(input, payloadSerializer.newReader(input));
+    }
+
+    public ObjectWriter<InterHubMessage> newWriter(OutputStream outputStream) {
+        Output output = new Output(outputStream);
+        return new MessageWriter(output, payloadSerializer.newWriter(output));
+    }
+
+    private static class MessageReader implements ObjectReader<InterHubMessage> {
+        private final Map<Integer, ChannelIdentifier> channels = new HashMap<Integer, ChannelIdentifier>();
+        private final Input input;
+        private final ObjectReader<?> payloadReader;
+
+        public MessageReader(Input input, ObjectReader<?> payloadReader) {
+            this.input = input;
+            this.payloadReader = payloadReader;
+        }
+
+        public InterHubMessage read() throws Exception {
+            switch (input.readByte()) {
+                case 1:
+                    ChannelIdentifier channelId = readChannelId();
+                    Object payload = payloadReader.read();
+                    return new ChannelMessage(channelId, payload);
+                case 2:
+                    return new EndOfStream();
+                default:
+                    throw new IllegalArgumentException();
+            }
+        }
+
+        private ChannelIdentifier readChannelId() {
+            int channelNum = input.readInt(true);
+            ChannelIdentifier channelId = channels.get(channelNum);
+            if (channelId == null) {
+                String channel = input.readString();
+                channelId = new ChannelIdentifier(channel);
+                channels.put(channelNum, channelId);
+            }
+            return channelId;
+        }
+    }
+
+    private static class MessageWriter implements ObjectWriter<InterHubMessage> {
+        private final Map<ChannelIdentifier, Integer> channels = new HashMap<ChannelIdentifier, Integer>();
+        private final Output output;
+        private final ObjectWriter<Object> payloadWriter;
+
+        public MessageWriter(Output output, ObjectWriter<Object> payloadWriter) {
+            this.output = output;
+            this.payloadWriter = payloadWriter;
+        }
+
+        public void write(InterHubMessage message) throws Exception {
+            if (message instanceof ChannelMessage) {
+                ChannelMessage channelMessage = (ChannelMessage) message;
+                output.writeByte(1);
+                writeChannelId(channelMessage);
+                payloadWriter.write(channelMessage.getPayload());
+            } else if (message instanceof EndOfStream) {
+                output.writeByte(2);
+            } else {
+                throw new IllegalArgumentException();
+            }
+            output.flush();
+        }
+
+        private void writeChannelId(ChannelMessage channelMessage) {
+            Integer channelNum = channels.get(channelMessage.getChannel());
+            if (channelNum == null) {
+                channelNum = channels.size();
+                channels.put(channelMessage.getChannel(), channelNum);
+                output.writeInt(channelNum, true);
+                output.writeString(channelMessage.getChannel().getName());
+            } else {
+                output.writeInt(channelNum, true);
+            }
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHub.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHub.java
new file mode 100644
index 0000000..85438cf
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHub.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub;
+
+import org.gradle.api.Action;
+import org.gradle.internal.concurrent.AsyncStoppable;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.hub.protocol.*;
+import org.gradle.messaging.remote.internal.hub.queue.EndPointQueue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A multi-channel message router.
+ */
+public class MessageHub implements AsyncStoppable {
+    private enum State {Running, Stopping, Stopped}
+
+    private static final Discard DISCARD = new Discard();
+    private final StoppableExecutor workers;
+    private final String displayName;
+    private final Action<? super Throwable> errorHandler;
+    private final Lock lock = new ReentrantLock();
+    private State state = State.Running;
+    private final IncomingQueue incomingQueue = new IncomingQueue(lock);
+    private final OutgoingQueue outgoingQueue = new OutgoingQueue(incomingQueue, lock);
+    private final ConnectionSet connections = new ConnectionSet(incomingQueue, outgoingQueue);
+
+    /**
+     * @param errorHandler Notified when some asynch. activity fails. Must be thread-safe.
+     */
+    public MessageHub(String displayName, ExecutorFactory executorFactory, Action<? super Throwable> errorHandler) {
+        this.displayName = displayName;
+        this.errorHandler = errorHandler;
+        workers = executorFactory.create(String.format("%s workers", displayName));
+    }
+
+    /**
+     * <p>Adds a {@link Dispatch} implementation that can be used to send outgoing messages on the given channel. The returned value is thread-safe.</p>
+     *
+     * <p>All messages sent via the dispatch are forwarded to exactly one connection.</p>
+     */
+    public <T> Dispatch<T> getOutgoing(final String channelName, final Class<T> type) {
+        lock.lock();
+        try {
+            assertRunning("create outgoing dispatch");
+            return new ChannelDispatch<T>(type, new ChannelIdentifier(channelName));
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Adds a handler for messages on the given channel. The handler may implement any of the following:
+     *
+     * <ul>
+     *
+     * <li>{@link Dispatch} to handle incoming messages received from any connections attached to this hub. Each incoming message is passed to exactly one handler associated to the given channel.
+     * </li>
+     *
+     * <li>{@link RejectedMessageListener} to receive notifications of outgoing messages that cannot be sent on the given channel.</li>
+     *
+     * <li>{@link HubStateListener} to receive notifications of state changes to this hub.</li>
+     *
+     * </ul>
+     *
+     * <p>The given handler does not need to be thread-safe, and is notified by at most one thread at a time. Multiple handlers can be added for a given channel.</p>
+     *
+     * <p>NOTE: If any method of the handler fails with an exception, the handler is discarded and will receive no further notifications.</p>
+     */
+    public void addHandler(String channelName, Object handler) {
+        lock.lock();
+        try {
+            assertRunning("add handler");
+
+            RejectedMessageListener rejectedMessageListener;
+            if (handler instanceof RejectedMessageListener) {
+                rejectedMessageListener = (RejectedMessageListener) handler;
+            } else {
+                rejectedMessageListener = DISCARD;
+            }
+            Dispatch<Object> dispatch;
+            if (handler instanceof Dispatch) {
+                dispatch = (Dispatch) handler;
+            } else {
+                dispatch = DISCARD;
+            }
+            HubStateListener stateListener;
+            if (handler instanceof HubStateListener) {
+                stateListener = (HubStateListener) handler;
+            } else {
+                stateListener = DISCARD;
+            }
+            ChannelIdentifier identifier = new ChannelIdentifier(channelName);
+            EndPointQueue queue = incomingQueue.getChannel(identifier).newEndpoint();
+            workers.execute(new Handler(queue, dispatch, rejectedMessageListener, stateListener));
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Adds a connection to some other message hub. Outgoing messages are forwarded to this connection, and incoming messages are received from it.
+     */
+    public void addConnection(Connection<InterHubMessage> connection) {
+        lock.lock();
+        try {
+            assertRunning("add connection");
+            ConnectionState connectionState = connections.add(connection);
+            workers.execute(new ConnectionDispatch(connectionState));
+            workers.execute(new ConnectionReceive(connectionState));
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void assertRunning(String action) {
+        if (state != State.Running) {
+            throw new IllegalStateException(String.format("Cannot %s, as %s has been stopped.", action, displayName));
+        }
+    }
+
+    /**
+     * Requests that this message hub commence shutting down. Does the following:
+     *
+     * <ul>
+     *
+     * <li>Stops accepting any further outgoing messages.</li>
+     *
+     * <li>If no connections are available, dispatches queued messages to any handlers that implement {@link RejectedMessageListener}.</li>
+     *
+     * </ul>
+     */
+    public void requestStop() {
+        lock.lock();
+        try {
+            if (state != State.Running) {
+                return;
+            }
+            try {
+                outgoingQueue.endOutput();
+                connections.requestStop();
+            } finally {
+                state = State.Stopping;
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Requests that this message hub stop. First requests stop as per {@link #requestStop()}, then blocks until stop has completed. This means that:
+     *
+     * <ul>
+     *
+     * <li>All calls to {@link Dispatch#dispatch(Object)} for outgoing messages have returned.</li>
+     *
+     * <li>All dispatches to handlers have completed.</li>
+     *
+     * <li>All internal threads have completed.</li>
+     *
+     * </ul>
+     */
+    public void stop() {
+        try {
+            lock.lock();
+            try {
+                requestStop();
+            } finally {
+                lock.unlock();
+            }
+            workers.stop();
+        } finally {
+            lock.lock();
+            try {
+                state = State.Stopped;
+            } finally {
+                lock.unlock();
+            }
+        }
+    }
+
+    private static class Discard implements Dispatch<Object>, RejectedMessageListener, HubStateListener {
+        public void dispatch(Object message) {
+        }
+
+        public void messageDiscarded(Object message) {
+        }
+
+        public void onConnect() {
+        }
+
+        public void onDisconnect() {
+        }
+    }
+
+    private class ConnectionReceive implements Runnable {
+        private final Connection<InterHubMessage> connection;
+        private final ConnectionState connectionState;
+
+        public ConnectionReceive(ConnectionState connectionState) {
+            this.connection = connectionState.getConnection();
+            this.connectionState = connectionState;
+        }
+
+        public void run() {
+            try {
+                try {
+                    while (true) {
+                        InterHubMessage message = connection.receive();
+                        if (message == null || message instanceof EndOfStream) {
+                            return;
+                        }
+                        lock.lock();
+                        try {
+                            incomingQueue.queue(message);
+                        } finally {
+                            lock.unlock();
+                        }
+                    }
+                } finally {
+                    lock.lock();
+                    try {
+                        connectionState.receiveFinished();
+                    } finally {
+                        lock.unlock();
+                    }
+                }
+            } catch (Throwable e) {
+                errorHandler.execute(e);
+            }
+        }
+    }
+
+    private class ConnectionDispatch implements Runnable {
+        private final Connection<InterHubMessage> connection;
+        private final EndPointQueue queue;
+        private final ConnectionState connectionState;
+
+        private ConnectionDispatch(ConnectionState connectionState) {
+            this.connection = connectionState.getConnection();
+            this.queue = connectionState.getDispatchQueue();
+            this.connectionState = connectionState;
+        }
+
+        public void run() {
+            try {
+                List<InterHubMessage> messages = new ArrayList<InterHubMessage>();
+                try {
+                    while (true) {
+                        lock.lock();
+                        try {
+                            queue.take(messages);
+                        } finally {
+                            lock.unlock();
+                        }
+                        for (Object message : messages) {
+                            InterHubMessage channelMessage = (InterHubMessage) message;
+                            connection.dispatch(channelMessage);
+                            if (message instanceof EndOfStream) {
+                                return;
+                            }
+                        }
+                        messages.clear();
+                    }
+                } finally {
+                    lock.lock();
+                    try {
+                        connectionState.dispatchFinished();
+                    } finally {
+                        lock.unlock();
+                    }
+                }
+            } catch (Throwable t) {
+                errorHandler.execute(t);
+            }
+        }
+    }
+
+    private class ChannelDispatch<T> implements Dispatch<T> {
+        private final Class<T> type;
+        private final ChannelIdentifier channelIdentifier;
+
+        public ChannelDispatch(Class<T> type, ChannelIdentifier channelIdentifier) {
+            this.type = type;
+            this.channelIdentifier = channelIdentifier;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("Dispatch %s to %s channel %s", type.getSimpleName(), displayName, channelIdentifier);
+        }
+
+        public void dispatch(T message) {
+            lock.lock();
+            try {
+                assertRunning("dispatch message");
+                outgoingQueue.dispatch(new ChannelMessage(channelIdentifier, message));
+            } finally {
+                lock.unlock();
+            }
+        }
+    }
+
+    private class Handler implements Runnable {
+        private final EndPointQueue queue;
+        private final Dispatch<Object> dispatch;
+        private final RejectedMessageListener listener;
+        private final HubStateListener stateListener;
+
+        public Handler(EndPointQueue queue, Dispatch<Object> dispatch, RejectedMessageListener listener, HubStateListener stateListener) {
+            this.queue = queue;
+            this.dispatch = dispatch;
+            this.listener = listener;
+            this.stateListener = stateListener;
+        }
+
+        public void run() {
+            try {
+                List<InterHubMessage> messages = new ArrayList<InterHubMessage>();
+                try {
+                    while (true) {
+                        lock.lock();
+                        try {
+                            queue.take(messages);
+                        } finally {
+                            lock.unlock();
+                        }
+                        for (InterHubMessage message : messages) {
+                            if (message instanceof EndOfStream) {
+                                return;
+                            }
+                            if (message instanceof ChannelMessage) {
+                                ChannelMessage channelMessage = (ChannelMessage) message;
+                                dispatch.dispatch(channelMessage.getPayload());
+                            } else if (message instanceof RejectedMessage) {
+                                RejectedMessage rejectedMessage = (RejectedMessage) message;
+                                listener.messageDiscarded(rejectedMessage.getPayload());
+                            } else if (message instanceof ConnectionEstablished) {
+                                stateListener.onConnect();
+                            } else if (message instanceof ConnectionClosed) {
+                                stateListener.onDisconnect();
+                            } else {
+                                throw new IllegalArgumentException(String.format("Don't know how to handle message %s", message));
+                            }
+                        }
+                        messages.clear();
+                    }
+                } finally {
+                    lock.lock();
+                    try {
+                        queue.stop();
+                    } finally {
+                        lock.unlock();
+                    }
+                }
+            } catch (Throwable t) {
+                errorHandler.execute(t);
+            }
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedClient.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedClient.java
new file mode 100644
index 0000000..1c5bd32
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedClient.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub;
+
+import org.gradle.api.Action;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.MessagingClient;
+import org.gradle.messaging.remote.ObjectConnection;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.MessageSerializer;
+import org.gradle.messaging.remote.internal.OutgoingConnector;
+import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MessageHubBackedClient implements MessagingClient {
+    private static final Logger LOGGER = LoggerFactory.getLogger(MessageHubBackedClient.class);
+    private final OutgoingConnector connector;
+    private final MessageSerializer<InterHubMessage> serializer;
+    private final ExecutorFactory executorFactory;
+
+    public MessageHubBackedClient(OutgoingConnector connector, MessageSerializer<InterHubMessage> serializer, ExecutorFactory executorFactory) {
+        this.connector = connector;
+        this.serializer = serializer;
+        this.executorFactory = executorFactory;
+    }
+
+    public ObjectConnection getConnection(Address address) {
+        Connection<InterHubMessage> connection = connector.connect(address, serializer);
+        MessageHub hub = new MessageHub(connection.toString(), executorFactory, new Action<Throwable>() {
+            public void execute(Throwable throwable) {
+                LOGGER.error("Unexpected exception thrown.", throwable);
+            }
+        });
+        return new MessageHubBackedObjectConnection(hub, connection);
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedObjectConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedObjectConnection.java
new file mode 100644
index 0000000..47b05c3
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedObjectConnection.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub;
+
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.concurrent.ThreadSafe;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.dispatch.ProxyDispatchAdapter;
+import org.gradle.messaging.dispatch.ReflectionDispatch;
+import org.gradle.messaging.remote.ObjectConnection;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage;
+
+public class MessageHubBackedObjectConnection implements ObjectConnection {
+    private final MessageHub hub;
+    private final Connection<InterHubMessage> connection;
+
+    public MessageHubBackedObjectConnection(MessageHub hub, Connection<InterHubMessage> connection) {
+        this.hub = hub;
+        this.connection = connection;
+        hub.addConnection(connection);
+    }
+
+    public void addIncoming(Class<?> type, Dispatch<? super MethodInvocation> dispatch) {
+        hub.addHandler(type.getName(), dispatch);
+    }
+
+    public <T> void addIncoming(Class<T> type, T instance) {
+        hub.addHandler(type.getName(), new ReflectionDispatch(instance));
+    }
+
+    public <T> T addOutgoing(Class<T> type) {
+        ProxyDispatchAdapter<T> adapter = new ProxyDispatchAdapter<T>(hub.getOutgoing(type.getName(), MethodInvocation.class), type, ThreadSafe.class);
+        return adapter.getSource();
+    }
+
+    public void requestStop() {
+        hub.requestStop();
+    }
+
+    public void stop() {
+        CompositeStoppable.stoppable(hub, connection).stop();
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedServer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedServer.java
new file mode 100644
index 0000000..923dac9
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MessageHubBackedServer.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub;
+
+import org.gradle.api.Action;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.messaging.remote.*;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.IncomingConnector;
+import org.gradle.messaging.remote.internal.MessageSerializer;
+import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MessageHubBackedServer implements MessagingServer {
+    private static final Logger LOGGER = LoggerFactory.getLogger(MessageHubBackedServer.class);
+    private final IncomingConnector connector;
+    private final MessageSerializer<InterHubMessage> serializer;
+    private final ExecutorFactory executorFactory;
+
+    public MessageHubBackedServer(IncomingConnector connector, MessageSerializer<InterHubMessage> serializer, ExecutorFactory executorFactory) {
+        this.connector = connector;
+        this.serializer = serializer;
+        this.executorFactory = executorFactory;
+    }
+
+    public ConnectionAcceptor accept(Action<ConnectEvent<ObjectConnection>> action) {
+        return connector.accept(new ConnectEventAction(action), serializer, false);
+    }
+
+    private class ConnectEventAction implements Action<ConnectEvent<Connection<InterHubMessage>>> {
+        private final Action<ConnectEvent<ObjectConnection>> action;
+
+        public ConnectEventAction(Action<ConnectEvent<ObjectConnection>> action) {
+            this.action = action;
+        }
+
+        public void execute(ConnectEvent<Connection<InterHubMessage>> connectEvent) {
+            Connection<InterHubMessage> connection = connectEvent.getConnection();
+            MessageHub hub = new MessageHub(connection.toString(), executorFactory, new Action<Throwable>() {
+                public void execute(Throwable throwable) {
+                    LOGGER.error("Unexpected exception thrown.", throwable);
+                }
+            });
+            MessageHubBackedObjectConnection objectConnection = new MessageHubBackedObjectConnection(hub, connection);
+            action.execute(new ConnectEvent<ObjectConnection>(objectConnection, connectEvent.getLocalAddress(), connectEvent.getRemoteAddress()));
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MethodInvocationSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MethodInvocationSerializer.java
new file mode 100644
index 0000000..29e1fc1
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/MethodInvocationSerializer.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub;
+
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.serialize.ObjectReader;
+import org.gradle.messaging.serialize.ObjectWriter;
+import org.gradle.messaging.serialize.kryo.KryoAwareSerializer;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MethodInvocationSerializer implements KryoAwareSerializer<MethodInvocation> {
+    private final ClassLoader classLoader;
+    private final KryoAwareSerializer<Object[]> argsSerializer;
+
+    public MethodInvocationSerializer(ClassLoader classLoader, KryoAwareSerializer<Object[]> argsSerializer) {
+        this.classLoader = classLoader;
+        this.argsSerializer = argsSerializer;
+    }
+
+    public ObjectReader<MethodInvocation> newReader(Input input) {
+        return new MethodInvocationReader(input, classLoader, argsSerializer.newReader(input));
+    }
+
+    public ObjectWriter<MethodInvocation> newWriter(Output output) {
+        return new MethodInvocationWriter(output, argsSerializer.newWriter(output));
+    }
+
+    private static class MethodInvocationWriter implements ObjectWriter<MethodInvocation> {
+        private final Output output;
+        private final ObjectWriter<Object[]> argsWriter;
+        private final Map<Method, Integer> methods = new HashMap<Method, Integer>();
+
+        public MethodInvocationWriter(Output output, ObjectWriter<Object[]> argsWriter) {
+            this.output = output;
+            this.argsWriter = argsWriter;
+        }
+
+        public void write(MethodInvocation value) throws Exception {
+            if (value.getArguments().length != value.getMethod().getParameterTypes().length) {
+                throw new IllegalArgumentException(String.format("Mismatched number of parameters to method %s.", value.getMethod()));
+            }
+            writeMethod(value.getMethod());
+            writeArguments(value);
+        }
+
+        private void writeArguments(MethodInvocation value) throws Exception {
+            argsWriter.write(value.getArguments());
+        }
+
+        private void writeMethod(Method method) {
+            Integer methodId = methods.get(method);
+            if (methodId == null) {
+                methodId = methods.size();
+                methods.put(method, methodId);
+                output.writeInt(methodId, true);
+                output.writeString(method.getDeclaringClass().getName());
+                output.writeString(method.getName());
+                output.writeInt(method.getParameterTypes().length, true);
+                for (Class<?> paramType : method.getParameterTypes()) {
+                    output.writeString(paramType.getName());
+                }
+            } else {
+                output.writeInt(methodId, true);
+            }
+        }
+    }
+
+    private static class MethodInvocationReader implements ObjectReader<MethodInvocation> {
+        private static final Map<String, Class<?>> PRIMITIVE_TYPES;
+        static {
+            PRIMITIVE_TYPES = new HashMap<String, Class<?>>();
+            PRIMITIVE_TYPES.put(Integer.TYPE.getName(), Integer.TYPE);
+        }
+
+        private final Input input;
+        private final ClassLoader classLoader;
+        private final ObjectReader<Object[]> argsReader;
+        private final Map<Integer, Method> methods = new HashMap<Integer, Method>();
+
+        public MethodInvocationReader(Input input, ClassLoader classLoader, ObjectReader<Object[]> argsReader) {
+            this.input = input;
+            this.classLoader = classLoader;
+            this.argsReader = argsReader;
+        }
+
+        public MethodInvocation read() throws Exception {
+            Method method = readMethod();
+            Object[] args = readArguments();
+            return new MethodInvocation(method, args);
+        }
+
+        private Object[] readArguments() throws Exception {
+            return argsReader.read();
+        }
+
+        private Method readMethod() throws ClassNotFoundException, NoSuchMethodException {
+            int methodId = input.readInt(true);
+            Method method = methods.get(methodId);
+            if (method == null) {
+                Class<?> declaringClass = readType();
+                String methodName = input.readString();
+                int paramCount = input.readInt(true);
+                Class<?>[] paramTypes = new Class<?>[paramCount];
+                for (int i = 0; i < paramTypes.length; i++) {
+                    paramTypes[i] = readType();
+                }
+                method = declaringClass.getDeclaredMethod(methodName, paramTypes);
+                methods.put(methodId, method);
+            }
+            return method;
+        }
+
+        private Class<?> readType() throws ClassNotFoundException {
+            String typeName = input.readString();
+            Class<?> paramType = PRIMITIVE_TYPES.get(typeName);
+            if (paramType == null) {
+                paramType = classLoader.loadClass(typeName);
+            }
+            return paramType;
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/OutgoingQueue.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/OutgoingQueue.java
new file mode 100644
index 0000000..ac7a662
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/OutgoingQueue.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub;
+
+import org.gradle.messaging.remote.internal.hub.protocol.ChannelMessage;
+import org.gradle.messaging.remote.internal.hub.protocol.EndOfStream;
+import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage;
+import org.gradle.messaging.remote.internal.hub.protocol.RejectedMessage;
+import org.gradle.messaging.remote.internal.hub.queue.MultiEndPointQueue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.locks.Lock;
+
+class OutgoingQueue extends MultiEndPointQueue {
+    private final IncomingQueue incomingQueue;
+
+    OutgoingQueue(IncomingQueue incomingQueue, Lock lock) {
+        super(lock);
+        this.incomingQueue = incomingQueue;
+    }
+
+    void endOutput() {
+        dispatch(new EndOfStream());
+    }
+
+    void discardQueued() {
+        List<InterHubMessage> rejected = new ArrayList<InterHubMessage>();
+        drain(rejected);
+        for (InterHubMessage message : rejected) {
+            if (message instanceof ChannelMessage) {
+                ChannelMessage channelMessage = (ChannelMessage) message;
+                incomingQueue.queue(new RejectedMessage(channelMessage.getChannel(), channelMessage.getPayload()));
+            }
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/RejectedMessageListener.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/RejectedMessageListener.java
new file mode 100644
index 0000000..180e2e2
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/RejectedMessageListener.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub;
+
+/**
+ * A listener which receives messages that cannot be delivered for some reason.
+ */
+public interface RejectedMessageListener {
+    /**
+     * Called when the given message cannot be delivered for some reason.
+     */
+    void messageDiscarded(Object message);
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/ChannelIdentifier.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/ChannelIdentifier.java
new file mode 100644
index 0000000..d57231f
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/ChannelIdentifier.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub.protocol;
+
+public class ChannelIdentifier {
+    private final String name;
+
+    public ChannelIdentifier(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+        ChannelIdentifier other = (ChannelIdentifier) obj;
+        return name.equals(other.name);
+    }
+
+    @Override
+    public int hashCode() {
+        return name.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return name;
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/ChannelMessage.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/ChannelMessage.java
new file mode 100644
index 0000000..7a51220
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/ChannelMessage.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub.protocol;
+
+public class ChannelMessage extends InterHubMessage implements Routable {
+    private final ChannelIdentifier channel;
+    private final Object payload;
+
+    public ChannelMessage(ChannelIdentifier channel, Object payload) {
+        this.channel = channel;
+        this.payload = payload;
+    }
+
+    @Override
+    public Delivery getDelivery() {
+        return Delivery.SingleHandler;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[%s channel: %s, payload: %s]", getClass().getSimpleName(), channel, payload);
+    }
+
+    public ChannelIdentifier getChannel() {
+        return channel;
+    }
+
+    public Object getPayload() {
+        return payload;
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/ConnectionClosed.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/ConnectionClosed.java
new file mode 100644
index 0000000..68dc6f5
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/ConnectionClosed.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub.protocol;
+
+import org.gradle.messaging.remote.internal.Connection;
+
+public class ConnectionClosed extends InterHubMessage {
+    private final Connection<InterHubMessage> connection;
+
+    public ConnectionClosed(Connection<InterHubMessage> connection) {
+        this.connection = connection;
+    }
+
+    public Connection<InterHubMessage> getConnection() {
+        return connection;
+    }
+
+    @Override
+    public Delivery getDelivery() {
+        return Delivery.Stateful;
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/ConnectionEstablished.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/ConnectionEstablished.java
new file mode 100644
index 0000000..5ed009c
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/ConnectionEstablished.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub.protocol;
+
+import org.gradle.messaging.remote.internal.Connection;
+
+public class ConnectionEstablished extends InterHubMessage {
+    private final Connection<InterHubMessage> connection;
+
+    public ConnectionEstablished(Connection<InterHubMessage> connection) {
+        this.connection = connection;
+    }
+
+    public Connection<InterHubMessage> getConnection() {
+        return connection;
+    }
+
+    @Override
+    public Delivery getDelivery() {
+        return Delivery.Stateful;
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/EndOfStream.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/EndOfStream.java
new file mode 100644
index 0000000..bb10e94
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/EndOfStream.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub.protocol;
+
+public class EndOfStream extends InterHubMessage {
+    @Override
+    public String toString() {
+        return String.format("[%s]", getClass().getSimpleName());
+    }
+
+    @Override
+    public Delivery getDelivery() {
+        return Delivery.Stateful;
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/InterHubMessage.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/InterHubMessage.java
new file mode 100644
index 0000000..58bc9ae
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/InterHubMessage.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub.protocol;
+
+public abstract class InterHubMessage {
+    /**
+     * Specifies the delivery constraints for this message.
+     */
+    public enum Delivery {
+        /**
+         * Message should be delivered to a single handler, and queued until a handler is available.
+         */
+        SingleHandler,
+        /**
+         * Message should be delivered to all current handlers. Discarded if no handler is available.
+         */
+        AllHandlers,
+        /**
+         * Message should be delivered to all handlers. Most recent stateful message is queued and also delivered to each new handler.
+         */
+        Stateful
+    }
+
+    public abstract Delivery getDelivery();
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/RejectedMessage.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/RejectedMessage.java
new file mode 100644
index 0000000..1534926
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/RejectedMessage.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub.protocol;
+
+public class RejectedMessage extends InterHubMessage implements Routable {
+    private final ChannelIdentifier channel;
+    private final Object payload;
+
+    public RejectedMessage(ChannelIdentifier channel, Object payload) {
+        this.channel = channel;
+        this.payload = payload;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[%s channel:%s, payload:%s]", getClass().getSimpleName(), channel, payload);
+    }
+
+    @Override
+    public Delivery getDelivery() {
+        return Delivery.AllHandlers;
+    }
+
+    public ChannelIdentifier getChannel() {
+        return channel;
+    }
+
+    public Object getPayload() {
+        return payload;
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/Routable.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/Routable.java
new file mode 100644
index 0000000..0c2ea95
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/protocol/Routable.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub.protocol;
+
+public interface Routable {
+    ChannelIdentifier getChannel();
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/queue/EndPointQueue.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/queue/EndPointQueue.java
new file mode 100644
index 0000000..e28e221
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/queue/EndPointQueue.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub.queue;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.locks.Condition;
+
+public class EndPointQueue implements Dispatch<InterHubMessage> {
+    private final List<InterHubMessage> queue = new ArrayList<InterHubMessage>();
+    private final MultiEndPointQueue owner;
+    private final Condition condition;
+
+    public EndPointQueue(MultiEndPointQueue owner, Condition condition) {
+        this.owner = owner;
+        this.condition = condition;
+    }
+
+    public void dispatch(InterHubMessage message) {
+        queue.add(message);
+        condition.signalAll();
+    }
+
+    public void take(Collection<InterHubMessage> drainTo) {
+        if (queue.isEmpty()) {
+            owner.empty(this);
+        }
+        while (queue.isEmpty()) {
+            try {
+                condition.await();
+            } catch (InterruptedException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+        drainTo.addAll(queue);
+        queue.clear();
+    }
+
+    public void stop() {
+        owner.stopped(this);
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/queue/MultiChannelQueue.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/queue/MultiChannelQueue.java
new file mode 100644
index 0000000..94e2d20
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/queue/MultiChannelQueue.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub.queue;
+
+import org.gradle.messaging.remote.internal.hub.protocol.ChannelIdentifier;
+import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage;
+import org.gradle.messaging.remote.internal.hub.protocol.Routable;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+
+import static org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage.Delivery.Stateful;
+
+public class MultiChannelQueue {
+    private final Lock lock;
+    private final Map<ChannelIdentifier, MultiEndPointQueue> channels = new HashMap<ChannelIdentifier, MultiEndPointQueue>();
+    private final QueueInitializer initializer = new QueueInitializer();
+
+    public MultiChannelQueue(Lock lock) {
+        this.lock = lock;
+    }
+
+    public MultiEndPointQueue getChannel(ChannelIdentifier channel) {
+        MultiEndPointQueue queue = channels.get(channel);
+        if (queue == null) {
+            queue = new MultiEndPointQueue(lock);
+            channels.put(channel, queue);
+            initializer.onQueueAdded(queue);
+        }
+        return queue;
+    }
+
+    public void queue(InterHubMessage message) {
+        if (message.getDelivery() == Stateful) {
+            initializer.onStatefulMessage(message);
+        }
+        if (message instanceof Routable) {
+            Routable routableMessage = (Routable) message;
+            getChannel(routableMessage.getChannel()).dispatch(message);
+        } else if (message.getDelivery() == Stateful || message.getDelivery() == InterHubMessage.Delivery.AllHandlers) {
+            for (MultiEndPointQueue queue : channels.values()) {
+                queue.dispatch(message);
+            }
+        } else {
+            throw new IllegalArgumentException(String.format("Don't know how to route message %s.", message));
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/queue/MultiEndPointQueue.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/queue/MultiEndPointQueue.java
new file mode 100644
index 0000000..b4f4197
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/queue/MultiEndPointQueue.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub.queue;
+
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.remote.internal.hub.protocol.EndOfStream;
+import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage;
+
+import java.util.*;
+import java.util.concurrent.locks.Lock;
+
+// TODO - use circular buffers to avoid copying
+// TODO - share a single initializer with MultiChannelQueue
+public class MultiEndPointQueue implements Dispatch<InterHubMessage> {
+    private final Set<EndPointQueue> endpoints = new HashSet<EndPointQueue>();
+    private final List<InterHubMessage> queue = new ArrayList<InterHubMessage>();
+    private final List<EndPointQueue> waiting = new ArrayList<EndPointQueue>();
+    private final Lock lock;
+    private final QueueInitializer initializer = new QueueInitializer();
+
+    public MultiEndPointQueue(Lock lock) {
+        this.lock = lock;
+    }
+
+    public void dispatch(InterHubMessage message) {
+        queue.add(message);
+        flush();
+    }
+
+    void empty(EndPointQueue endPointQueue) {
+        waiting.add(endPointQueue);
+        flush();
+    }
+
+    void stopped(EndPointQueue queue) {
+        waiting.remove(queue);
+        endpoints.remove(queue);
+        queue.dispatch(new EndOfStream());
+    }
+
+    public void drain(Collection<InterHubMessage> drainTo) {
+        drainTo.addAll(queue);
+        queue.clear();
+    }
+
+    private void flush() {
+        // TODO - need to do a better job of routing messages when there are multiple endpoints. This is just going to forward all queued messages to the first
+        // waiting endpoint, even if there are multiple waiting to do work
+        EndPointQueue selected = waiting.isEmpty() ? null : waiting.get(0);
+        while (!queue.isEmpty()) {
+            InterHubMessage message = queue.get(0);
+            switch (message.getDelivery()) {
+                case Stateful:
+                case AllHandlers:
+                    if (endpoints.isEmpty()) {
+                        return;
+                    }
+                    if (message.getDelivery() == InterHubMessage.Delivery.Stateful) {
+                        initializer.onStatefulMessage(message);
+                    }
+                    for (EndPointQueue endpoint : endpoints) {
+                        endpoint.dispatch(message);
+                    }
+                    queue.remove(0);
+                    waiting.clear();
+                    continue;
+                case SingleHandler:
+                    if (selected == null) {
+                        return;
+                    }
+                    queue.remove(0);
+                    waiting.remove(selected);
+                    selected.dispatch(message);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unknown delivery type: " + message.getDelivery());
+            }
+        }
+    }
+
+    public EndPointQueue newEndpoint() {
+        EndPointQueue endPointQueue = new EndPointQueue(this, lock.newCondition());
+        endpoints.add(endPointQueue);
+        initializer.onQueueAdded(endPointQueue);
+        return endPointQueue;
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/queue/QueueInitializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/queue/QueueInitializer.java
new file mode 100644
index 0000000..2842141
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/hub/queue/QueueInitializer.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub.queue;
+
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.hub.protocol.ConnectionClosed;
+import org.gradle.messaging.remote.internal.hub.protocol.ConnectionEstablished;
+import org.gradle.messaging.remote.internal.hub.protocol.EndOfStream;
+import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class QueueInitializer {
+    private EndOfStream endOfStream;
+    private Map<Connection<?>, ConnectionEstablished> queued = new LinkedHashMap<Connection<?>, ConnectionEstablished>();
+
+    void onStatefulMessage(InterHubMessage message) {
+        if (message instanceof ConnectionEstablished) {
+            ConnectionEstablished connectionEstablished = (ConnectionEstablished) message;
+            queued.put(connectionEstablished.getConnection(), connectionEstablished);
+        } else if (message instanceof ConnectionClosed) {
+            ConnectionClosed connectionClosed = (ConnectionClosed) message;
+            queued.remove(connectionClosed.getConnection());
+        } else if (message instanceof EndOfStream) {
+            queued.clear();
+            endOfStream = (EndOfStream) message;
+        } else {
+            throw new UnsupportedOperationException(String.format("Received unexpected stateful message: %s", message));
+        }
+    }
+
+    void onQueueAdded(Dispatch<InterHubMessage> queue) {
+        for (ConnectionEstablished connectionEstablished : queued.values()) {
+            queue.dispatch(connectionEstablished);
+        }
+        if (endOfStream != null) {
+            queue.dispatch(endOfStream);
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/MulticastConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/MulticastConnection.java
index 2c48478..2a55df2 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/MulticastConnection.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/MulticastConnection.java
@@ -52,9 +52,8 @@ public class MulticastConnection<T> implements Connection<T> {
     public void dispatch(T message) {
         try {
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-            DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
-            serializer.write(message, dataOutputStream);
-            dataOutputStream.close();
+            serializer.newWriter(outputStream).write(message);
+            outputStream.close();
             byte[] buffer = outputStream.toByteArray();
             socket.send(new DatagramPacket(buffer, buffer.length, address.getAddress(), address.getPort()));
         } catch (Exception e) {
@@ -68,8 +67,7 @@ public class MulticastConnection<T> implements Connection<T> {
             DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
             socket.receive(packet);
             ByteArrayInputStream inputStream = new ByteArrayInputStream(packet.getData(), packet.getOffset(), packet.getLength());
-            DataInputStream dataInputStream = new DataInputStream(inputStream);
-            return serializer.read(dataInputStream, localAddress, new SocketInetAddress(packet.getAddress(), packet.getPort()));
+            return serializer.newReader(inputStream, localAddress, new SocketInetAddress(packet.getAddress(), packet.getPort())).read();
         } catch (SocketException e) {
             // Assume closed
             return null;
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/SocketConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/SocketConnection.java
index c5ee93f..bcf40c2 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/SocketConnection.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/SocketConnection.java
@@ -23,8 +23,13 @@ import org.gradle.messaging.remote.Address;
 import org.gradle.messaging.remote.internal.Connection;
 import org.gradle.messaging.remote.internal.MessageIOException;
 import org.gradle.messaging.remote.internal.MessageSerializer;
+import org.gradle.messaging.serialize.ObjectReader;
+import org.gradle.messaging.serialize.ObjectWriter;
 
-import java.io.*;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.net.InetSocketAddress;
 import java.nio.ByteBuffer;
 import java.nio.channels.ClosedSelectorException;
@@ -36,19 +41,19 @@ public class SocketConnection<T> implements Connection<T> {
     private final SocketChannel socket;
     private final SocketInetAddress localAddress;
     private final SocketInetAddress remoteAddress;
-    private final MessageSerializer<T> serializer;
-    private final DataInputStream instr;
-    private final DataOutputStream outstr;
+    private final ObjectWriter<T> objectWriter;
+    private final ObjectReader<T> objectReader;
+    private final InputStream instr;
+    private final OutputStream outstr;
 
     public SocketConnection(SocketChannel socket, MessageSerializer<T> serializer) {
         this.socket = socket;
-        this.serializer = serializer;
         try {
             // NOTE: we use non-blocking IO as there is no reliable way when using blocking IO to shutdown reads while
             // keeping writes active. For example, Socket.shutdownInput() does not work on Windows.
             socket.configureBlocking(false);
-            outstr = new DataOutputStream(new SocketOutputStream(socket));
-            instr = new DataInputStream(new SocketInputStream(socket));
+            outstr = new SocketOutputStream(socket);
+            instr = new SocketInputStream(socket);
         } catch (IOException e) {
             throw UncheckedException.throwAsUncheckedException(e);
         }
@@ -56,6 +61,8 @@ public class SocketConnection<T> implements Connection<T> {
         localAddress = new SocketInetAddress(localSocketAddress.getAddress(), localSocketAddress.getPort());
         InetSocketAddress remoteSocketAddress = (InetSocketAddress) socket.socket().getRemoteSocketAddress();
         remoteAddress = new SocketInetAddress(remoteSocketAddress.getAddress(), remoteSocketAddress.getPort());
+        objectReader = serializer.newReader(instr, localAddress, remoteAddress);
+        objectWriter = serializer.newWriter(outstr);
     }
 
     @Override
@@ -73,7 +80,7 @@ public class SocketConnection<T> implements Connection<T> {
 
     public T receive() {
         try {
-            return serializer.read(instr, localAddress, remoteAddress);
+            return objectReader.read();
         } catch (Exception e) {
             if (isEndOfStream(e)) {
                 return null;
@@ -102,7 +109,7 @@ public class SocketConnection<T> implements Connection<T> {
 
     public void dispatch(T message) {
         try {
-            serializer.write(message, outstr);
+            objectWriter.write(message);
             outstr.flush();
         } catch (Exception e) {
             throw new MessageIOException(String.format("Could not write message %s to '%s'.", message, remoteAddress), e);
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpIncomingConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpIncomingConnector.java
index b195e78..37c40a3 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpIncomingConnector.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpIncomingConnector.java
@@ -19,13 +19,14 @@ package org.gradle.messaging.remote.internal.inet;
 import org.gradle.api.Action;
 import org.gradle.internal.CompositeStoppable;
 import org.gradle.internal.UncheckedException;
-import org.gradle.internal.id.IdGenerator;
-import org.gradle.internal.concurrent.AsyncStoppable;
 import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.internal.id.IdGenerator;
 import org.gradle.messaging.remote.Address;
 import org.gradle.messaging.remote.ConnectEvent;
+import org.gradle.messaging.remote.ConnectionAcceptor;
 import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.DefaultMessageSerializer;
 import org.gradle.messaging.remote.internal.IncomingConnector;
 import org.gradle.messaging.remote.internal.MessageSerializer;
 import org.slf4j.Logger;
@@ -37,29 +38,28 @@ import java.nio.channels.ClosedChannelException;
 import java.nio.channels.ServerSocketChannel;
 import java.nio.channels.SocketChannel;
 import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
 
-public class TcpIncomingConnector<T> implements IncomingConnector<T>, AsyncStoppable {
+public class TcpIncomingConnector implements IncomingConnector {
     private static final Logger LOGGER = LoggerFactory.getLogger(TcpIncomingConnector.class);
-    private final StoppableExecutor executor;
-    private final MessageSerializer<T> serializer;
+    private final ExecutorFactory executorFactory;
     private final InetAddressFactory addressFactory;
     private final IdGenerator<?> idGenerator;
-    private final List<ServerSocketChannel> serverSockets = new CopyOnWriteArrayList<ServerSocketChannel>();
 
-    public TcpIncomingConnector(ExecutorFactory executorFactory, MessageSerializer<T> serializer, InetAddressFactory addressFactory, IdGenerator<?> idGenerator) {
-        this.serializer = serializer;
+    public TcpIncomingConnector(ExecutorFactory executorFactory, InetAddressFactory addressFactory, IdGenerator<?> idGenerator) {
+        this.executorFactory = executorFactory;
         this.addressFactory = addressFactory;
         this.idGenerator = idGenerator;
-        this.executor = executorFactory.create("Incoming TCP Connector");
     }
 
-    public Address accept(Action<ConnectEvent<Connection<T>>> action, boolean allowRemote) {
-        ServerSocketChannel serverSocket;
+    public <T> ConnectionAcceptor accept(Action<ConnectEvent<Connection<T>>> action, ClassLoader classLoader, boolean allowRemote) {
+        return accept(action, new DefaultMessageSerializer<T>(classLoader), allowRemote);
+    }
+
+    public <T> ConnectionAcceptor accept(Action<ConnectEvent<Connection<T>>> action, MessageSerializer<T> serializer, boolean allowRemote) {
+        final ServerSocketChannel serverSocket;
         int localPort;
         try {
             serverSocket = ServerSocketChannel.open();
-            serverSockets.add(serverSocket);
             serverSocket.socket().bind(new InetSocketAddress(0));
             localPort = serverSocket.socket().getLocalPort();
         } catch (Exception e) {
@@ -68,30 +68,38 @@ public class TcpIncomingConnector<T> implements IncomingConnector<T>, AsyncStopp
 
         Object id = idGenerator.generateId();
         List<InetAddress> addresses = allowRemote ? addressFactory.findRemoteAddresses() : addressFactory.findLocalAddresses();
-        Address address = new MultiChoiceAddress(id, localPort, addresses);
+        final Address address = new MultiChoiceAddress(id, localPort, addresses);
         LOGGER.debug("Listening on {}.", address);
 
-        executor.execute(new Receiver(serverSocket, action, allowRemote));
-        return address;
-    }
+        final StoppableExecutor executor = executorFactory.create(String.format("Incoming %s TCP Connector on port %s", allowRemote ? "remote" : "local", localPort));
+        executor.execute(new Receiver<T>(serverSocket, action, serializer, allowRemote));
 
-    public void requestStop() {
-        CompositeStoppable.stoppable(serverSockets).stop();
-    }
+        return new ConnectionAcceptor() {
+            public Address getAddress() {
+                return address;
+            }
 
-    public void stop() {
-        requestStop();
-        executor.stop();
+            public void requestStop() {
+                CompositeStoppable.stoppable(serverSocket).stop();
+            }
+
+            public void stop() {
+                requestStop();
+                executor.stop();
+            }
+        };
     }
 
-    private class Receiver implements Runnable {
+    private class Receiver<T> implements Runnable {
         private final ServerSocketChannel serverSocket;
         private final Action<ConnectEvent<Connection<T>>> action;
+        private final MessageSerializer<T> serializer;
         private final boolean allowRemote;
 
-        public Receiver(ServerSocketChannel serverSocket, Action<ConnectEvent<Connection<T>>> action, boolean allowRemote) {
+        public Receiver(ServerSocketChannel serverSocket, Action<ConnectEvent<Connection<T>>> action, MessageSerializer<T> serializer, boolean allowRemote) {
             this.serverSocket = serverSocket;
             this.action = action;
+            this.serializer = serializer;
             this.allowRemote = allowRemote;
         }
 
@@ -122,7 +130,6 @@ public class TcpIncomingConnector<T> implements IncomingConnector<T>, AsyncStopp
                 }
             } finally {
                 CompositeStoppable.stoppable(serverSocket).stop();
-                serverSockets.remove(serverSocket);
             }
         }
     }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpOutgoingConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpOutgoingConnector.java
index 330810f..5ba5761 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpOutgoingConnector.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpOutgoingConnector.java
@@ -17,10 +17,7 @@
 package org.gradle.messaging.remote.internal.inet;
 
 import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.internal.ConnectException;
-import org.gradle.messaging.remote.internal.Connection;
-import org.gradle.messaging.remote.internal.MessageSerializer;
-import org.gradle.messaging.remote.internal.OutgoingConnector;
+import org.gradle.messaging.remote.internal.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -30,15 +27,14 @@ import java.net.SocketException;
 import java.nio.channels.SocketChannel;
 import java.util.List;
 
-public class TcpOutgoingConnector<T> implements OutgoingConnector<T> {
+public class TcpOutgoingConnector implements OutgoingConnector {
     private static final Logger LOGGER = LoggerFactory.getLogger(TcpOutgoingConnector.class);
-    private final MessageSerializer<T> serializer;
 
-    public TcpOutgoingConnector(MessageSerializer<T> serializer) {
-        this.serializer = serializer;
+    public <T> Connection<T> connect(Address destinationAddress, ClassLoader messageClassLoader) throws ConnectException {
+        return connect(destinationAddress, new DefaultMessageSerializer<T>(messageClassLoader));
     }
 
-    public Connection<T> connect(Address destinationAddress) {
+    public <T> Connection<T> connect(Address destinationAddress, MessageSerializer<T> serializer) throws ConnectException {
         if (!(destinationAddress instanceof InetEndpoint)) {
             throw new IllegalArgumentException(String.format("Cannot create a connection to address of unknown type: %s.", destinationAddress));
         }
@@ -57,22 +53,18 @@ public class TcpOutgoingConnector<T> implements OutgoingConnector<T> {
                 SocketChannel socketChannel;
                 try {
                     socketChannel = SocketChannel.open(new InetSocketAddress(candidate, address.getPort()));
-                } catch (java.net.ConnectException e) {
-                    LOGGER.debug("Cannot connect to address {}, skipping.", candidate);
-                    lastFailure = e;
-                    continue;
                 } catch (SocketException e) {
                     LOGGER.debug("Cannot connect to address {}, skipping.", candidate);
-                    lastFailure = new RuntimeException(String.format("Could not connect to address %s.", candidate), e);
+                    lastFailure = e;
                     continue;
                 }
                 LOGGER.debug("Connected to address {}.", candidate);
                 return new SocketConnection<T>(socketChannel, serializer);
             }
-            throw lastFailure;
-        } catch (java.net.ConnectException e) {
             throw new ConnectException(String.format("Could not connect to server %s. Tried addresses: %s.",
-                    destinationAddress, candidateAddresses), e);
+                    destinationAddress, candidateAddresses), lastFailure);
+        } catch (ConnectException e) {
+            throw e;
         } catch (Exception e) {
             throw new RuntimeException(String.format("Could not connect to server %s. Tried addresses: %s.",
                     destinationAddress, candidateAddresses), e);
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConnectRequest.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConnectRequest.java
deleted file mode 100755
index a6e8667..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConnectRequest.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.messaging.remote.internal.protocol;
-
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.internal.Message;
-
-public class ConnectRequest extends Message {
-    private final Address destinationAddress;
-
-    public ConnectRequest(Address destinationAddress) {
-        this.destinationAddress = destinationAddress;
-    }
-
-    public Address getDestinationAddress() {
-        return destinationAddress;
-    }
-}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/DiscoveryMessage.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/DiscoveryMessage.java
index 3b3507b..cc22cc8 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/DiscoveryMessage.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/DiscoveryMessage.java
@@ -17,9 +17,7 @@ package org.gradle.messaging.remote.internal.protocol;
 
 import org.gradle.messaging.remote.internal.MessageOriginator;
 
-import java.io.Serializable;
-
-public class DiscoveryMessage implements Serializable {
+public class DiscoveryMessage {
     private final MessageOriginator originator;
     private final String group;
 
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/DiscoveryProtocolSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/DiscoveryProtocolSerializer.java
index c1cc23b..bc17f4a 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/DiscoveryProtocolSerializer.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/DiscoveryProtocolSerializer.java
@@ -15,149 +15,177 @@
  */
 package org.gradle.messaging.remote.internal.protocol;
 
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
 import org.gradle.messaging.remote.Address;
 import org.gradle.messaging.remote.internal.MessageOriginator;
 import org.gradle.messaging.remote.internal.MessageSerializer;
 import org.gradle.messaging.remote.internal.inet.InetEndpoint;
 import org.gradle.messaging.remote.internal.inet.MultiChoiceAddress;
+import org.gradle.messaging.serialize.ObjectReader;
+import org.gradle.messaging.serialize.ObjectWriter;
 
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
+import java.io.*;
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
 public class DiscoveryProtocolSerializer implements MessageSerializer<DiscoveryMessage> {
-    public static final byte PROTOCOL_VERSION = 1;
+    public static final byte PROTOCOL_VERSION = 2;
     public static final byte LOOKUP_REQUEST = 1;
     public static final byte CHANNEL_AVAILABLE = 2;
     public static final byte CHANNEL_UNAVAILABLE = 3;
 
-    public DiscoveryMessage read(DataInputStream inputStream, InetEndpoint localAddress, InetEndpoint remoteAddress) throws Exception {
-        byte protocolVersion = inputStream.readByte();
-        if (protocolVersion != PROTOCOL_VERSION) {
-            return new UnknownMessage(String.format("unknown protocol version %s", protocolVersion));
-        }
-        byte messageType = inputStream.readByte();
-        switch (messageType) {
-            case LOOKUP_REQUEST:
-                return readLookupRequest(inputStream);
-            case CHANNEL_AVAILABLE:
-                return readChannelAvailable(inputStream, remoteAddress);
-            case CHANNEL_UNAVAILABLE:
-                return readChannelUnavailable(inputStream);
-        }
-        return new UnknownMessage(String.format("unknown message type %s", messageType));
+    public ObjectReader<DiscoveryMessage> newReader(InputStream inputStream, Address localAddress, Address remoteAddress) {
+        return new MessageReader(inputStream, (InetEndpoint) remoteAddress);
     }
 
-    private DiscoveryMessage readChannelUnavailable(DataInputStream inputStream) throws IOException {
-        MessageOriginator originator = readMessageOriginator(inputStream);
-        String group = inputStream.readUTF();
-        String channel = inputStream.readUTF();
-        Address address = readAddress(inputStream);
-        return new ChannelUnavailable(originator, group, channel, address);
+    public ObjectWriter<DiscoveryMessage> newWriter(OutputStream outputStream) {
+        return new MessageWriter(outputStream);
     }
 
-    private DiscoveryMessage readChannelAvailable(DataInputStream inputStream, InetEndpoint remoteAddress) throws IOException {
-        MessageOriginator originator = readMessageOriginator(inputStream);
-        String group = inputStream.readUTF();
-        String channel = inputStream.readUTF();
-        MultiChoiceAddress address = readAddress(inputStream);
-        address = address.addAddresses(remoteAddress.getCandidates());
-        return new ChannelAvailable(originator, group, channel, address);
-    }
+    private static class MessageReader implements ObjectReader<DiscoveryMessage> {
+        private final Input input;
+        private final InetEndpoint remoteAddress;
 
-    private DiscoveryMessage readLookupRequest(DataInputStream inputStream) throws IOException {
-        MessageOriginator originator = readMessageOriginator(inputStream);
-        String group = inputStream.readUTF();
-        String channel = inputStream.readUTF();
-        return new LookupRequest(originator, group, channel);
-    }
+        public MessageReader(InputStream inputStream, InetEndpoint remoteAddress) {
+            this.input = new Input(inputStream);
+            this.remoteAddress = remoteAddress;
+        }
 
-    private MessageOriginator readMessageOriginator(DataInputStream inputStream) throws IOException {
-        UUID uuid = readUUID(inputStream);
-        String name = inputStream.readUTF();
-        return new MessageOriginator(uuid, name);
-    }
+        public DiscoveryMessage read() throws Exception {
+            byte protocolVersion = input.readByte();
+            if (protocolVersion != PROTOCOL_VERSION) {
+                return new UnknownMessage(String.format("unknown protocol version %s", protocolVersion));
+            }
+            byte messageType = input.readByte();
+            switch (messageType) {
+                case LOOKUP_REQUEST:
+                    return readLookupRequest();
+                case CHANNEL_AVAILABLE:
+                    return readChannelAvailable();
+                case CHANNEL_UNAVAILABLE:
+                    return readChannelUnavailable();
+            }
+            return new UnknownMessage(String.format("unknown message type %s", messageType));
+        }
 
-    private MultiChoiceAddress readAddress(DataInputStream inputStream) throws IOException {
-        UUID uuid = readUUID(inputStream);
-        int port = inputStream.readInt();
-        int addressCount = inputStream.readInt();
-        List<InetAddress> addresses = new ArrayList<InetAddress>();
-        for (int i = 0; i < addressCount; i++) {
-            int length = inputStream.readInt();
-            byte[] binAddress = new byte[length];
-            inputStream.readFully(binAddress);
-            InetAddress inetAddress = InetAddress.getByAddress(binAddress);
-            addresses.add(inetAddress);
-        }
-        return new MultiChoiceAddress(uuid, port, addresses);
-    }
+        private DiscoveryMessage readChannelUnavailable() throws IOException {
+            MessageOriginator originator = readMessageOriginator();
+            String group = input.readString();
+            String channel = input.readString();
+            Address address = readAddress();
+            return new ChannelUnavailable(originator, group, channel, address);
+        }
 
-    private UUID readUUID(DataInputStream inputStream) throws IOException {
-        long mostSigUuidBits = inputStream.readLong();
-        long leastSigUuidBits = inputStream.readLong();
-        return new UUID(mostSigUuidBits, leastSigUuidBits);
-    }
+        private DiscoveryMessage readChannelAvailable() throws IOException {
+            MessageOriginator originator = readMessageOriginator();
+            String group = input.readString();
+            String channel = input.readString();
+            MultiChoiceAddress address = readAddress();
+            address = address.addAddresses(remoteAddress.getCandidates());
+            return new ChannelAvailable(originator, group, channel, address);
+        }
 
-    public void write(DiscoveryMessage message, DataOutputStream outputStream) throws Exception {
-        outputStream.writeByte(PROTOCOL_VERSION);
-        if (message instanceof LookupRequest) {
-            writeLookupRequest(outputStream, (LookupRequest) message);
-        } else if (message instanceof ChannelAvailable) {
-            writeChannelAvailable(outputStream, (ChannelAvailable) message);
-        } else if (message instanceof ChannelUnavailable) {
-            writeChannelUnavailable(outputStream, (ChannelUnavailable) message);
-        } else {
-            throw new UnsupportedOperationException();
+        private DiscoveryMessage readLookupRequest() throws IOException {
+            MessageOriginator originator = readMessageOriginator();
+            String group = input.readString();
+            String channel = input.readString();
+            return new LookupRequest(originator, group, channel);
         }
-    }
 
-    private void writeChannelUnavailable(DataOutputStream outputStream, ChannelUnavailable channelUnavailable) throws IOException {
-        outputStream.writeByte(CHANNEL_UNAVAILABLE);
-        writeMessageOriginator(outputStream, channelUnavailable.getOriginator());
-        outputStream.writeUTF(channelUnavailable.getGroup());
-        outputStream.writeUTF(channelUnavailable.getChannel());
-        writeAddress(outputStream, (MultiChoiceAddress) channelUnavailable.getAddress());
-    }
+        private MessageOriginator readMessageOriginator() throws IOException {
+            UUID uuid = readUUID();
+            String name = input.readString();
+            return new MessageOriginator(uuid, name);
+        }
 
-    private void writeChannelAvailable(DataOutputStream outputStream, ChannelAvailable channelAvailable) throws IOException {
-        outputStream.writeByte(CHANNEL_AVAILABLE);
-        writeMessageOriginator(outputStream, channelAvailable.getOriginator());
-        outputStream.writeUTF(channelAvailable.getGroup());
-        outputStream.writeUTF(channelAvailable.getChannel());
-        writeAddress(outputStream, (MultiChoiceAddress) channelAvailable.getAddress());
-    }
+        private MultiChoiceAddress readAddress() throws IOException {
+            UUID uuid = readUUID();
+            int port = input.readInt(true);
+            int addressCount = input.readInt(true);
+            List<InetAddress> addresses = new ArrayList<InetAddress>();
+            for (int i = 0; i < addressCount; i++) {
+                int length = input.readInt(true);
+                byte[] binAddress = input.readBytes(length);
+                InetAddress inetAddress = InetAddress.getByAddress(binAddress);
+                addresses.add(inetAddress);
+            }
+            return new MultiChoiceAddress(uuid, port, addresses);
+        }
 
-    private void writeLookupRequest(DataOutputStream outputStream, LookupRequest request) throws IOException {
-        outputStream.writeByte(LOOKUP_REQUEST);
-        writeMessageOriginator(outputStream, request.getOriginator());
-        outputStream.writeUTF(request.getGroup());
-        outputStream.writeUTF(request.getChannel());
+        private UUID readUUID() throws IOException {
+            long mostSigUuidBits = input.readLong();
+            long leastSigUuidBits = input.readLong();
+            return new UUID(mostSigUuidBits, leastSigUuidBits);
+        }
     }
 
-    private void writeMessageOriginator(DataOutputStream outputStream, MessageOriginator messageOriginator) throws IOException {
-        writeUUID(outputStream, messageOriginator.getId());
-        outputStream.writeUTF(messageOriginator.getName());
-    }
+    private static class MessageWriter implements ObjectWriter<DiscoveryMessage> {
+        private final Output output;
 
-    private void writeAddress(DataOutputStream outputStream, MultiChoiceAddress address) throws IOException {
-        writeUUID(outputStream, address.getCanonicalAddress());
-        outputStream.writeInt(address.getPort());
-        outputStream.writeInt(address.getCandidates().size());
-        for (InetAddress inetAddress : address.getCandidates()) {
-            byte[] binAddress = inetAddress.getAddress();
-            outputStream.writeInt(binAddress.length);
-            outputStream.write(binAddress);
+        public MessageWriter(OutputStream outputStream) {
+            this.output = new Output(outputStream);
+        }
+
+        public void write(DiscoveryMessage message) throws Exception {
+            output.writeByte(PROTOCOL_VERSION);
+            if (message instanceof LookupRequest) {
+                writeLookupRequest((LookupRequest) message);
+            } else if (message instanceof ChannelAvailable) {
+                writeChannelAvailable((ChannelAvailable) message);
+            } else if (message instanceof ChannelUnavailable) {
+                writeChannelUnavailable((ChannelUnavailable) message);
+            } else {
+                throw new UnsupportedOperationException();
+            }
+            output.flush();
+        }
+
+        private void writeChannelUnavailable(ChannelUnavailable channelUnavailable) throws IOException {
+            output.writeByte(CHANNEL_UNAVAILABLE);
+            writeMessageOriginator(channelUnavailable.getOriginator());
+            output.writeString(channelUnavailable.getGroup());
+            output.writeString(channelUnavailable.getChannel());
+            writeAddress((MultiChoiceAddress) channelUnavailable.getAddress());
+        }
+
+        private void writeChannelAvailable(ChannelAvailable channelAvailable) throws IOException {
+            output.writeByte(CHANNEL_AVAILABLE);
+            writeMessageOriginator(channelAvailable.getOriginator());
+            output.writeString(channelAvailable.getGroup());
+            output.writeString(channelAvailable.getChannel());
+            writeAddress((MultiChoiceAddress) channelAvailable.getAddress());
         }
-    }
 
-    private void writeUUID(DataOutputStream outputStream, Object id) throws IOException {
-        UUID uuid = (UUID) id;
-        outputStream.writeLong(uuid.getMostSignificantBits());
-        outputStream.writeLong(uuid.getLeastSignificantBits());
+        private void writeLookupRequest(LookupRequest request) throws IOException {
+            output.writeByte(LOOKUP_REQUEST);
+            writeMessageOriginator(request.getOriginator());
+            output.writeString(request.getGroup());
+            output.writeString(request.getChannel());
+        }
+
+        private void writeMessageOriginator(MessageOriginator messageOriginator) throws IOException {
+            writeUUID(messageOriginator.getId());
+            output.writeString(messageOriginator.getName());
+        }
+
+        private void writeAddress(MultiChoiceAddress address) throws IOException {
+            writeUUID(address.getCanonicalAddress());
+            output.writeInt(address.getPort(), true);
+            output.writeInt(address.getCandidates().size(), true);
+            for (InetAddress inetAddress : address.getCandidates()) {
+                byte[] binAddress = inetAddress.getAddress();
+                output.writeInt(binAddress.length, true);
+                output.write(binAddress);
+            }
+        }
+
+        private void writeUUID(Object id) throws IOException {
+            UUID uuid = (UUID) id;
+            output.writeLong(uuid.getMostSignificantBits());
+            output.writeLong(uuid.getLeastSignificantBits());
+        }
     }
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/DataStreamBackedSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/DataStreamBackedSerializer.java
new file mode 100644
index 0000000..9b40ce6
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/DataStreamBackedSerializer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.serialize;
+
+import java.io.*;
+
+public abstract class DataStreamBackedSerializer<T> implements Serializer<T> {
+    public T read(InputStream instr) throws Exception {
+        DataInputStream dataInputStream = new DataInputStream(instr);
+        return read((DataInput) dataInputStream);
+    }
+
+    public void write(OutputStream outstr, T value) throws Exception {
+        DataOutputStream output = new DataOutputStream(outstr);
+        write((DataOutput) output, value);
+        output.flush();
+    }
+
+    public abstract T read(DataInput dataInput) throws Exception;
+
+    public abstract void write(DataOutput dataOutput, T value) throws IOException;
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/DefaultSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/DefaultSerializer.java
new file mode 100644
index 0000000..cf9a115
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/DefaultSerializer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.messaging.serialize;
+
+import org.gradle.internal.io.ClassLoaderObjectInputStream;
+
+import java.io.*;
+
+public class DefaultSerializer<T> implements Serializer<T> {
+    private ClassLoader classLoader;
+
+    public DefaultSerializer() {
+        classLoader = getClass().getClassLoader();
+    }
+
+    public DefaultSerializer(ClassLoader classLoader) {
+        this.classLoader = classLoader != null ? classLoader : getClass().getClassLoader();
+    }
+
+    public ClassLoader getClassLoader() {
+        return classLoader;
+    }
+
+    public void setClassLoader(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
+    public T read(InputStream instr) throws Exception {
+        try {
+            return (T) new ClassLoaderObjectInputStream(instr, classLoader).readObject();
+        } catch (StreamCorruptedException e) {
+            return null;
+        }
+    }
+
+    public void write(OutputStream outstr, T value) throws IOException {
+        ObjectOutputStream objectStr = new ObjectOutputStream(outstr);
+        objectStr.writeObject(value);
+        objectStr.flush();
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/ObjectReader.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/ObjectReader.java
new file mode 100644
index 0000000..58e7927
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/ObjectReader.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.serialize;
+
+public interface ObjectReader<T> {
+    T read() throws Exception;
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/ObjectWriter.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/ObjectWriter.java
new file mode 100644
index 0000000..341cde8
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/ObjectWriter.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.serialize;
+
+public interface ObjectWriter<T> {
+    void write(T value) throws Exception;
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/Serializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/Serializer.java
index d349f54..9388801 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/Serializer.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/Serializer.java
@@ -19,7 +19,14 @@ import java.io.InputStream;
 import java.io.OutputStream;
 
 public interface Serializer<T> {
+    /**
+     * Reads the next object from the given stream. The implementation must not perform any buffering, so that it reads only those bytes from the input stream that are
+     * required to deserialize the next object.
+     */
     T read(InputStream instr) throws Exception;
 
+    /**
+     * Writes the given object to the given stream. The implementation must not perform any buffering.
+     */
     void write(OutputStream outstr, T value) throws Exception;
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/JavaSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/JavaSerializer.java
new file mode 100644
index 0000000..86f4a06
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/JavaSerializer.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.serialize.kryo;
+
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import org.gradle.messaging.remote.internal.Message;
+import org.gradle.messaging.serialize.ObjectReader;
+import org.gradle.messaging.serialize.ObjectWriter;
+
+public class JavaSerializer<T> implements KryoAwareSerializer<T> {
+    private final ClassLoader classLoader;
+
+    public JavaSerializer(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
+    public ObjectReader<T> newReader(Input input) {
+        return new JavaReader<T>(input, classLoader);
+    }
+
+    public ObjectWriter<T> newWriter(Output output) {
+        return new JavaWriter<T>(output);
+    }
+
+    private static class JavaReader<T> implements ObjectReader<T> {
+        private final Input input;
+        private final ClassLoader classLoader;
+
+        private JavaReader(Input input, ClassLoader classLoader) {
+            this.input = input;
+            this.classLoader = classLoader;
+        }
+
+        public T read() throws Exception {
+            return (T) Message.receive(input, classLoader);
+        }
+    }
+
+    private class JavaWriter<T> implements ObjectWriter<T> {
+        private final Output output;
+
+        public JavaWriter(Output output) {
+            this.output = output;
+        }
+
+        public void write(T value) throws Exception {
+            Message.send(value, output);
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/KryoAwareSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/KryoAwareSerializer.java
new file mode 100644
index 0000000..8d6608b
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/KryoAwareSerializer.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.serialize.kryo;
+
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import org.gradle.messaging.serialize.ObjectReader;
+import org.gradle.messaging.serialize.ObjectWriter;
+
+public interface KryoAwareSerializer<T> {
+    ObjectReader<T> newReader(Input input);
+
+    ObjectWriter<T> newWriter(Output output);
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/KryoSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/KryoSerializer.java
new file mode 100644
index 0000000..a0d5293
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/KryoSerializer.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.serialize.kryo;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import org.gradle.messaging.serialize.ObjectReader;
+import org.gradle.messaging.serialize.ObjectWriter;
+
+public class KryoSerializer<T> implements KryoAwareSerializer<T> {
+    public ObjectReader<T> newReader(Input input) {
+        return new KryoReader<T>(input);
+    }
+
+    public ObjectWriter<T> newWriter(Output output) {
+        return new KryoWriter<T>(output);
+    }
+
+    private static class KryoReader<T> implements ObjectReader<T> {
+        private final Input input;
+
+        private KryoReader(Input input) {
+            this.input = input;
+        }
+
+        public T read() throws Exception {
+            Kryo kryo = new Kryo();
+            return (T) kryo.readClassAndObject(input);
+        }
+    }
+
+    private class KryoWriter<T> implements ObjectWriter<T> {
+        private final Output output;
+
+        public KryoWriter(Output output) {
+            this.output = output;
+        }
+
+        public void write(T value) throws Exception {
+            Kryo kryo = new Kryo();
+            kryo.writeClassAndObject(output, value);
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/TypeSafeSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/TypeSafeSerializer.java
new file mode 100644
index 0000000..5af1c77
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/kryo/TypeSafeSerializer.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.serialize.kryo;
+
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import org.gradle.messaging.serialize.ObjectReader;
+import org.gradle.messaging.serialize.ObjectWriter;
+
+public class TypeSafeSerializer<T> implements KryoAwareSerializer<Object> {
+    private final Class<T> type;
+    private final KryoAwareSerializer<T> serializer;
+
+    public TypeSafeSerializer(Class<T> type, KryoAwareSerializer<T> serializer) {
+        this.type = type;
+        this.serializer = serializer;
+    }
+
+    public ObjectReader<Object> newReader(Input input) {
+        final ObjectReader<T> reader = serializer.newReader(input);
+        return new ObjectReader<Object>() {
+            public Object read() throws Exception {
+                return reader.read();
+            }
+        };
+    }
+
+    public ObjectWriter<Object> newWriter(final Output output) {
+        final ObjectWriter<T> writer = serializer.newWriter(output);
+        return new ObjectWriter<Object>() {
+            public void write(Object value) throws Exception {
+                writer.write(type.cast(value));
+            }
+        };
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/actor/internal/DefaultActorFactorySpec.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/actor/internal/DefaultActorFactorySpec.groovy
new file mode 100644
index 0000000..cd89411
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/actor/internal/DefaultActorFactorySpec.groovy
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.actor.internal
+
+import org.gradle.internal.concurrent.ThreadSafe
+import org.gradle.messaging.dispatch.DispatchException
+import org.gradle.messaging.dispatch.MethodInvocation
+import org.gradle.test.fixtures.concurrent.ConcurrentSpec
+
+class DefaultActorFactorySpec extends ConcurrentSpec {
+    private final TargetObject target = Mock()
+    private final DefaultActorFactory factory = new DefaultActorFactory(executorFactory)
+
+    def cleanup() {
+        factory.stop()
+    }
+
+    def createsANonBlockingActorForATargetObject() {
+        when:
+        def actor = factory.createActor(target)
+
+        then:
+        actor != null
+    }
+
+    def cachesTheNonBlockingActorForATargetObject() {
+        when:
+        def actor1 = factory.createActor(target)
+        def actor2 = factory.createActor(target)
+
+        then:
+        actor2.is(actor1)
+    }
+
+    def returnsTargetObjectIfTargetObjectIsAnActor() {
+        when:
+        def actor1 = factory.createActor(target)
+        def actor2 = factory.createActor(actor1)
+
+        then:
+        actor2.is(actor1)
+    }
+
+    def nonBlockingActorAndProxyAreBothMarkedAsThreadSafe() {
+        when:
+        def actor = factory.createActor(target)
+
+        then:
+        actor instanceof ThreadSafe
+        actor.getProxy(Runnable) instanceof ThreadSafe
+    }
+
+    def createsABlockingActorForATargetObject() {
+        when:
+        def actor = factory.createBlockingActor(target)
+
+        then:
+        actor != null
+    }
+
+    def cachesTheBlockingActorForATargetObject() {
+        when:
+        def actor1 = factory.createBlockingActor(target)
+        def actor2 = factory.createBlockingActor(target)
+
+        then:
+        actor1.is(actor2)
+    }
+
+    def blockingActorAndProxyAreBothMarkedAsThreadSafe() {
+        when:
+        def actor = factory.createBlockingActor(target)
+
+        then:
+        actor instanceof ThreadSafe
+        actor.getProxy(Runnable) instanceof ThreadSafe
+    }
+
+    def blockingActorDispatchesMethodInvocationToTargetObjectAndBlocksUntilMethodIsComplete() {
+        def actor = factory.createBlockingActor(target)
+        def testThread = Thread.currentThread()
+
+        when:
+        actor.dispatch(new MethodInvocation(TargetObject.class.getMethod('doStuff', String.class), ['param'] as Object[]))
+
+        then:
+        1 * target.doStuff('param') >> {
+            assert Thread.currentThread() == testThread
+        }
+    }
+
+    def blockingActorProxyDispatchesMethodInvocationToTargetObjectAndBlocksUntilMethodIsComplete() {
+        def actor = factory.createBlockingActor(target)
+        def proxy = actor.getProxy(TargetObject)
+        def testThread = Thread.currentThread()
+
+        when:
+        proxy.doStuff('param')
+
+        then:
+        1 * target.doStuff('param') >> {
+            assert Thread.currentThread() == testThread
+        }
+    }
+
+    def blockingActorDispatchesMethodInvocationFromOneThreadAtATime() {
+        def target = {param ->
+            if (param == 'param') {
+                instant.param1Start
+                thread.block()
+                instant.param1End
+            } else if (param == 'param2') {
+                instant.param2Start
+            }
+        } as TargetObject
+
+        def actor = factory.createBlockingActor(target)
+        def proxy = actor.getProxy(TargetObject)
+
+        when:
+        start {
+            proxy.doStuff('param')
+        }
+        async {
+            thread.blockUntil.param1Start
+            proxy.doStuff('param2')
+        }
+
+        then:
+        instant.param2Start > instant.param1End
+    }
+
+    def nonBlockingActorDispatchesMethodInvocationToTargetObjectAndDoesNotWaitForResult() {
+        def actor = factory.createActor(target)
+
+        when:
+        operation.dispatch {
+            actor.dispatch(new MethodInvocation(TargetObject.class.getMethod('doStuff', String.class), ['param'] as Object[]))
+        }
+        thread.blockUntil.handled
+
+        then:
+        1 * target.doStuff('param') >> {
+            thread.block()
+            instant.handled
+        }
+
+        and:
+        operation.dispatch.end < instant.handled
+    }
+
+    def nonBlockingActorProxyDispatchesMethodCallToTargetObjectAndDoesNotWaitForResult() {
+        def actor = factory.createActor(target)
+        def proxy = actor.getProxy(TargetObject.class)
+
+        when:
+        operation.dispatch {
+            proxy.doStuff('param')
+        }
+        thread.blockUntil.actionFinished
+
+        then:
+        1 * target.doStuff('param') >> {
+            thread.block()
+            instant.actionFinished
+        }
+
+        and:
+        operation.dispatch.end < instant.actionFinished
+    }
+
+    def nonBlockingActorPropagatesMethodFailuresOnStop() {
+        def actor = factory.createActor(target)
+        def proxy = actor.getProxy(TargetObject.class)
+        def failure = new RuntimeException()
+
+        given:
+        target.doStuff('param') >> { throw failure }
+
+        when:
+        proxy.doStuff('param')
+        actor.stop()
+
+        then:
+        DispatchException e = thrown()
+        e.message.startsWith("Could not dispatch message")
+        e.cause == failure
+    }
+
+    def nonBlockingActorStopBlocksUntilAllMethodCallsComplete() {
+        def actor = factory.createActor(target)
+        def proxy = actor.getProxy(TargetObject.class)
+
+        given:
+        target.doStuff('param') >> {
+            thread.block()
+            instant.param1
+        }
+        target.doStuff('param2') >> {
+            instant.param2
+        }
+
+        when:
+        operation.dispatchAndStop {
+            proxy.doStuff('param')
+            proxy.doStuff('param2')
+            actor.stop()
+        }
+
+        then:
+        operation.dispatchAndStop.end > instant.param1
+        operation.dispatchAndStop.end > instant.param2
+    }
+
+    def blockingActorStopBlocksUntilAllMethodCallsComplete() {
+        def actor = factory.createBlockingActor(target)
+        def proxy = actor.getProxy(TargetObject.class)
+
+        given:
+        target.doStuff('param') >> {
+            instant.actionStarted
+            thread.block()
+            instant.actionFinished
+        }
+
+        when:
+        start {
+            proxy.doStuff('param')
+        }
+        operation.stop {
+            thread.blockUntil.actionStarted
+            actor.stop()
+        }
+
+        then:
+        operation.stop.end > instant.actionFinished
+    }
+
+    def factoryStopBlocksUntilAllMethodCallsComplete() {
+        def actor = factory.createActor(target)
+        def proxy = actor.getProxy(TargetObject.class)
+
+        given:
+        target.doStuff('param') >> {
+            thread.block()
+            instant.actionFinished
+        }
+
+        when:
+        proxy.doStuff('param')
+        operation.stop {
+            factory.stop()
+        }
+
+        then:
+        operation.stop.end > instant.actionFinished
+    }
+
+    def cannotDispatchToBlockingActorAfterItHasBeenStopped() {
+        def actor = factory.createBlockingActor(target)
+        def proxy = actor.getProxy(TargetObject.class)
+
+        given:
+        actor.stop()
+
+        when:
+        proxy.doStuff('param')
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'This actor has been stopped.'
+    }
+
+    def cannotDispatchToNonBlockingActorAfterItHasBeenStopped() {
+        def actor = factory.createActor(target)
+        def proxy = actor.getProxy(TargetObject.class)
+
+        given:
+        actor.stop()
+
+        when:
+        proxy.doStuff('param')
+
+        then:
+        IllegalStateException e = thrown()
+        e.message.startsWith('Cannot dispatch message, as this message dispatch has been stopped.')
+    }
+}
+
+interface TargetObject {
+    void doStuff(String param)
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/actor/internal/DefaultActorFactoryTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/actor/internal/DefaultActorFactoryTest.groovy
deleted file mode 100644
index ec836b5..0000000
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/actor/internal/DefaultActorFactoryTest.groovy
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.messaging.actor.internal
-
-import org.gradle.messaging.actor.Actor
-import org.gradle.messaging.dispatch.DispatchException
-import org.gradle.messaging.dispatch.MethodInvocation
-import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.MultithreadedTestCase
-import org.jmock.integration.junit4.JMock
-import org.junit.After
-import org.junit.Test
-import org.junit.runner.RunWith
-import static org.junit.Assert.*
-import static org.hamcrest.Matchers.*
-import org.gradle.internal.concurrent.ThreadSafe
-
- at RunWith(JMock.class)
-class DefaultActorFactoryTest extends MultithreadedTestCase {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final TargetObject target = context.mock(TargetObject.class)
-    private final DefaultActorFactory factory = new DefaultActorFactory(executorFactory)
-
-    @After
-    public void tearDown() {
-        factory.stop()
-    }
-
-    @Test
-    public void createsANonBlockingActorForATargetObject() {
-        def actor = factory.createActor(target)
-        assertThat(actor, notNullValue())
-    }
-
-    @Test
-    public void cachesTheNonBlockingActorForATargetObject() {
-        Actor actor1 = factory.createActor(target)
-        Actor actor2 = factory.createActor(target)
-        assertThat(actor2, sameInstance(actor1))
-    }
-
-    @Test
-    public void returnsTargetObjectIfTargetObjectIsAnActor() {
-        Actor actor1 = factory.createActor(target)
-        Actor actor2 = factory.createActor(actor1)
-        assertThat(actor2, sameInstance(actor1))
-    }
-
-    @Test
-    public void nonBlockingActorAndProxyAreBothMarkedAsThreadSafe() {
-        def actor = factory.createActor(target)
-        assertThat(actor, instanceOf(ThreadSafe))
-        assertThat(actor.getProxy(Runnable), instanceOf(ThreadSafe))
-    }
-
-    @Test
-    public void createsABlockingActorForATargetObject() {
-        def actor = factory.createBlockingActor(target)
-        assertThat(actor, notNullValue())
-    }
-
-    @Test
-    public void cachesTheBlockingActorForATargetObject() {
-        Actor actor1 = factory.createBlockingActor(target)
-        Actor actor2 = factory.createBlockingActor(target)
-        assertThat(actor2, sameInstance(actor1))
-    }
-
-    @Test
-    public void blockingActorAndProxyAreBothMarkedAsThreadSafe() {
-        def actor = factory.createBlockingActor(target)
-        assertThat(actor, instanceOf(ThreadSafe))
-        assertThat(actor.getProxy(Runnable), instanceOf(ThreadSafe))
-    }
-
-    @Test
-    public void blockingActorDispatchesMethodInvocationToTargetObjectAndWaitsForResult() {
-        Actor actor = factory.createBlockingActor(target)
-
-        context.checking {
-            one(target).doStuff('param')
-        }
-
-        actor.dispatch(new MethodInvocation(TargetObject.class.getMethod('doStuff', String.class), ['param'] as Object[]))
-    }
-
-    @Test
-    public void blockingActorProxyDispatchesMethodInvocationToTargetObjectAndWaitsForResult() {
-        Actor actor = factory.createBlockingActor(target)
-
-        context.checking {
-            one(target).doStuff('param')
-        }
-
-        actor.getProxy(TargetObject).doStuff('param')
-    }
-
-    @Test
-    public void blockingActorDispatchesMethodInvocationFromOneThreadAtATime() {
-        Actor actor = factory.createBlockingActor(target)
-
-        context.checking {
-            one(target).doStuff('param')
-            will {
-                syncAt(1)
-            }
-            one(target).doStuff('param2')
-        }
-        
-        def proxy = actor.getProxy(TargetObject)
-        start {
-            proxy.doStuff('param')
-        }
-        run {
-            expectBlocksUntil(1) {
-                proxy.doStuff('param2')
-            }
-        }
-    }
-
-    @Test
-    public void nonBlockingActorDispatchesMethodInvocationToTargetObjectAndDoesNotWaitForResult() {
-        Actor actor = factory.createActor(target)
-
-        context.checking {
-            one(target).doStuff('param')
-            will {
-                syncAt(1)
-            }
-        }
-
-        run {
-            actor.dispatch(new MethodInvocation(TargetObject.class.getMethod('doStuff', String.class), ['param'] as Object[]))
-            syncAt(1)
-        }
-    }
-
-    @Test
-    public void nonBlockingActorProxyDispatchesMethodCallToTargetObjectAndDoesNotWaitForResult() {
-        Actor actor = factory.createActor(target)
-        TargetObject proxy = actor.getProxy(TargetObject.class)
-
-        context.checking {
-            one(target).doStuff('param')
-            will {
-                syncAt(1)
-            }
-        }
-
-        run {
-            proxy.doStuff('param')
-            syncAt(1)
-        }
-    }
-
-    @Test
-    public void nonBlockingActorPropagatesMethodFailuresOnStop() {
-        Actor actor = factory.createActor(target)
-        TargetObject proxy = actor.getProxy(TargetObject.class)
-        RuntimeException failure = new RuntimeException()
-
-        context.checking {
-            one(target).doStuff('param')
-            will {
-                throw failure
-            }
-        }
-
-        run {
-            proxy.doStuff('param')
-            try {
-                actor.stop()
-                fail()
-            } catch (DispatchException e) {
-                assertThat(e.message, startsWith("Could not dispatch message"))
-                assertThat(e.cause, sameInstance(failure))
-            }
-        }
-    }
-
-    @Test
-    public void nonBlockingActorStopBlocksUntilAllMethodCallsComplete() {
-        Actor actor = factory.createActor(target)
-        TargetObject proxy = actor.getProxy(TargetObject.class)
-
-        context.checking {
-            one(target).doStuff('param')
-            will {
-                syncAt(1)
-            }
-        }
-
-        run {
-            proxy.doStuff('param')
-            expectBlocksUntil(1) {
-                actor.stop()
-            }
-        }
-    }
-
-    @Test
-    public void blockingActorStopBlocksUntilAllMethodCallsComplete() {
-        Actor actor = factory.createBlockingActor(target)
-        TargetObject proxy = actor.getProxy(TargetObject.class)
-
-        context.checking {
-            one(target).doStuff('param')
-            will {
-                syncAt(1)
-            }
-        }
-
-        start {
-            proxy.doStuff('param')
-        }
-        run {
-            expectBlocksUntil(1) {
-                actor.stop()
-            }
-        }
-    }
-
-    @Test
-    public void factoryStopBlocksUntilAllMethodCallsComplete() {
-        Actor actor = factory.createActor(target)
-        TargetObject proxy = actor.getProxy(TargetObject.class)
-
-        context.checking {
-            one(target).doStuff('param')
-            will {
-                syncAt(1)
-            }
-        }
-
-        run {
-            proxy.doStuff('param')
-            expectBlocksUntil(1) {
-                factory.stop()
-            }
-        }
-    }
-    
-    @Test
-    public void cannotDispatchToBlockingActorAfterStopped() {
-        Actor actor = factory.createBlockingActor(target)
-        TargetObject proxy = actor.getProxy(TargetObject.class)
-
-        actor.stop()
-        try {
-            proxy.doStuff('param')
-            fail()
-        } catch (IllegalStateException e) {
-            // Expected
-        }
-    }
-
-    @Test
-    public void cannotDispatchToNonBlockingActorAfterStopped() {
-        Actor actor = factory.createActor(target)
-        TargetObject proxy = actor.getProxy(TargetObject.class)
-
-        actor.stop()
-        try {
-            proxy.doStuff('param')
-            fail()
-        } catch (IllegalStateException e) {
-            // Expected
-        }
-    }
-}
-
-interface TargetObject {
-    void doStuff(String param)
-
-    void stopDoingStuff()
-}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/CompositeAddressTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/CompositeAddressTest.groovy
deleted file mode 100644
index a392eb8..0000000
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/CompositeAddressTest.groovy
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.messaging.remote.internal
-
-import spock.lang.Specification
-import org.gradle.messaging.remote.Address
-import org.gradle.util.Matchers
-
-class CompositeAddressTest extends Specification {
-    def "has useful display name"() {
-        def target = { "<target>" } as Address
-        def qualifier = "<qualifier>"
-        def address = new CompositeAddress(target, qualifier)
-
-        expect:
-        address.displayName == "<target>:<qualifier>"
-        address.toString() == "<target>:<qualifier>"
-    }
-
-    def "equal when address and qualifier are equal"() {
-        def target = { "<target>" } as Address
-        def target2 = { "<target2>" } as Address
-        def address = new CompositeAddress(target, "qualifier")
-        def same = new CompositeAddress(target, "qualifier")
-        def differentAddress = new CompositeAddress(target2, "qualifier")
-        def differentQualifier = new CompositeAddress(target, "other")
-
-        expect:
-        address Matchers.strictlyEqual(same)
-        address != differentAddress
-        address != differentQualifier
-    }
-}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClientTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClientTest.groovy
deleted file mode 100644
index be484d4..0000000
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClientTest.groovy
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.messaging.remote.internal
-
-import spock.lang.Specification
-import org.gradle.messaging.remote.Address
-
-class DefaultMessagingClientTest extends Specification {
-    final MultiChannelConnector connector = Mock()
-    final DefaultMessagingClient client = new DefaultMessagingClient(connector, getClass().classLoader)
-
-    def "creates connection and stops connection on stop"() {
-        Address address = Mock()
-        MultiChannelConnection<Object> connection = Mock()
-
-        when:
-        def result = client.getConnection(address)
-
-        then:
-        1 * connector.connect(address) >> connection
-        result instanceof DefaultObjectConnection
-
-        when:
-        client.stop()
-
-        then:
-        1 * connection.stop()
-    }
-}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServerTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServerTest.groovy
deleted file mode 100644
index 6099101..0000000
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServerTest.groovy
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package org.gradle.messaging.remote.internal
-
-import spock.lang.Specification
-import org.gradle.api.Action
-import org.gradle.messaging.remote.ConnectEvent
-import org.gradle.messaging.remote.ObjectConnection
-import org.gradle.messaging.remote.Address
-
-class DefaultMessagingServerTest extends Specification {
-    final Address remoteAddress = Mock()
-    final Address localAddress = Mock()
-    private final MultiChannelConnector multiChannelConnector = Mock()
-    private final DefaultMessagingServer server = new DefaultMessagingServer(multiChannelConnector, getClass().classLoader)
-
-    def createsConnection() {
-        Action<ConnectEvent<ObjectConnection>> action = Mock()
-        Action<ConnectEvent<MultiChannelConnection<Message>>> wrappedAction = Mock()
-        MultiChannelConnection<Message> connection = Mock()
-
-        when:
-        def address = server.accept(action)
-
-        then:
-        address == localAddress
-        1 * multiChannelConnector.accept(!null) >> { wrappedAction = it[0]; return localAddress }
-
-        when:
-        wrappedAction.execute(new ConnectEvent(connection, localAddress, remoteAddress))
-
-        then:
-        1 * action.execute(!null) >> {
-            ConnectEvent event = it[0]
-            assert event.connection instanceof DefaultObjectConnection
-            assert event.localAddress == localAddress
-            assert event.remoteAddress == remoteAddress
-        }
-    }
-
-    def stopsConnectionsOnServerStop() {
-        MultiChannelConnection<Message> channelConnection = Mock()
-        expectConnectionCreated(channelConnection)
-
-        when:
-        server.stop()
-
-        then:
-        1 * channelConnection.stop()
-    }
-
-    def discardsConnectionOnStop() {
-        MultiChannelConnection<Message> channelConnection = Mock()
-        ObjectConnection connection = expectConnectionCreated(channelConnection)
-
-        when:
-        connection.stop()
-
-        then:
-        1 * channelConnection.stop()
-
-        when:
-        server.stop()
-
-        then:
-        0 * channelConnection._
-    }
-
-    private ObjectConnection expectConnectionCreated(MultiChannelConnection<Message> channelConnection) {
-        Action<ConnectEvent<ObjectConnection>> action = Mock()
-        ObjectConnection connection
-
-        1 * multiChannelConnector.accept(!null) >> {
-            def wrappedAction = it[0]
-            wrappedAction.execute(new ConnectEvent(channelConnection, localAddress, remoteAddress))
-        }
-        1 * action.execute(!null) >> {
-            connection = it[0].connection
-        }
-        server.accept(action)
-        return connection
-    }
-}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java
deleted file mode 100755
index 6baa1bf..0000000
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.messaging.remote.internal;
-
-import org.gradle.internal.concurrent.AsyncStoppable;
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.Addressable;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.gradle.util.Matchers.strictlyEqual;
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-
- at RunWith(JMock.class)
-public class DefaultObjectConnectionTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private DefaultObjectConnection sender;
-    private DefaultObjectConnection receiver;
-    private final Addressable messageConnection = context.mock(Addressable.class);
-    private final AsyncStoppable stopControl = context.mock(AsyncStoppable.class);
-    private final TestConnection connection = new TestConnection();
-
-    @Before
-    public void setUp() {
-        IncomingMethodInvocationHandler senderIncoming = new IncomingMethodInvocationHandler(connection.getSender());
-        IncomingMethodInvocationHandler receiverIncoming = new IncomingMethodInvocationHandler(connection.getReceiver());
-        OutgoingMethodInvocationHandler senderOutgoing = new OutgoingMethodInvocationHandler(connection.getSender());
-        OutgoingMethodInvocationHandler receiverOutgoing = new OutgoingMethodInvocationHandler(connection.getReceiver());
-        sender = new DefaultObjectConnection(messageConnection, stopControl, senderOutgoing, senderIncoming);
-        receiver = new DefaultObjectConnection(messageConnection, stopControl, receiverOutgoing, receiverIncoming);
-    }
-
-    @Test
-    public void createsProxyForOutgoingType() throws Exception {
-        // Setup
-        final TestRemote handler = context.mock(TestRemote.class);
-        receiver.addIncoming(TestRemote.class, handler);
-
-        TestRemote proxy = sender.addOutgoing(TestRemote.class);
-        assertThat(proxy, strictlyEqual(proxy));
-        assertThat(proxy.toString(), equalTo("TestRemote broadcast"));
-    }
-
-    @Test
-    public void deliversMethodInvocationsOnOutgoingObjectToHandlerObject() throws Exception {
-        final TestRemote handler = context.mock(TestRemote.class);
-        context.checking(new Expectations() {{
-            one(handler).doStuff("param");
-        }});
-        receiver.addIncoming(TestRemote.class, handler);
-
-        TestRemote proxy = sender.addOutgoing(TestRemote.class);
-        proxy.doStuff("param");
-    }
-
-    @Test
-    public void deliversMethodInvocationsOnOutgoingObjectToHandlerDispatch() throws Exception {
-        final Dispatch<MethodInvocation> handler = context.mock(Dispatch.class);
-        context.checking(new Expectations() {{
-            one(handler).dispatch(new MethodInvocation(TestRemote.class.getMethod("doStuff", String.class),
-                    new Object[]{"param"}));
-        }});
-        receiver.addIncoming(TestRemote.class, handler);
-
-        TestRemote proxy = sender.addOutgoing(TestRemote.class);
-        proxy.doStuff("param");
-    }
-
-    @Test
-    public void canHaveMultipleOutgoingTypes() throws Exception {
-        final TestRemote handler1 = context.mock(TestRemote.class);
-        final TestRemote2 handler2 = context.mock(TestRemote2.class);
-
-        context.checking(new Expectations() {{
-            one(handler1).doStuff("handler 1");
-            one(handler2).doStuff("handler 2");
-        }});
-        receiver.addIncoming(TestRemote.class, handler1);
-        receiver.addIncoming(TestRemote2.class, handler2);
-
-        TestRemote remote1 = sender.addOutgoing(TestRemote.class);
-        TestRemote2 remote2 = sender.addOutgoing(TestRemote2.class);
-
-        remote1.doStuff("handler 1");
-        remote2.doStuff("handler 2");
-    }
-
-    @Test
-    public void handlesTypesWithSuperTypes() {
-        final TestRemote3 handler = context.mock(TestRemote3.class);
-
-        context.checking(new Expectations() {{
-            one(handler).doStuff("handler 1");
-        }});
-        receiver.addIncoming(TestRemote3.class, handler);
-
-        TestRemote3 remote1 = sender.addOutgoing(TestRemote3.class);
-
-        remote1.doStuff("handler 1");
-    }
-
-    @Test
-    public void cannotRegisterMultipleHandlerObjectsWithSameType() {
-        TestRemote handler = context.mock(TestRemote.class);
-        receiver.addIncoming(TestRemote.class, handler);
-
-        try {
-            receiver.addIncoming(TestRemote.class, handler);
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), equalTo("A handler has already been added for type '" + TestRemote.class.getName() + "'."));
-        }
-    }
-
-    @Test
-    public void cannotRegisterMultipleHandlerObjectsWithOverlappingMethods() {
-        receiver.addIncoming(TestRemote3.class, context.mock(TestRemote3.class));
-
-        try {
-            receiver.addIncoming(TestRemote.class, context.mock(TestRemote.class));
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), equalTo("A handler has already been added for type '" + TestRemote.class.getName() + "'."));
-        }
-    }
-
-    @Test
-    public void canCreateMultipleOutgoingObjectsWithSameType() {
-        sender.addOutgoing(TestRemote.class);
-        sender.addOutgoing(TestRemote.class);
-    }
-
-    @Test
-    public void stopsConnectionOnStop() {
-        context.checking(new Expectations() {{
-            one(stopControl).stop();
-        }});
-
-        receiver.stop();
-    }
-
-    private class TestConnection {
-        Map<Object, Dispatch<Object>> channels = new HashMap<Object, Dispatch<Object>>();
-
-        MultiChannelConnection<Object> getSender() {
-            return new MultiChannelConnection<Object>() {
-                public Dispatch<Object> addOutgoingChannel(String channelKey) {
-                    return channels.get(channelKey);
-                }
-
-                public void addIncomingChannel(String channelKey, Dispatch<Object> dispatch) {
-                    throw new UnsupportedOperationException();
-                }
-
-                public void requestStop() {
-                    throw new UnsupportedOperationException();
-                }
-
-                public void stop() {
-                    throw new UnsupportedOperationException();
-                }
-
-                public Address getLocalAddress() {
-                    throw new UnsupportedOperationException();
-                }
-
-                public Address getRemoteAddress() {
-                    throw new UnsupportedOperationException();
-                }
-            };
-        }
-
-        MultiChannelConnection<Object> getReceiver() {
-            return new MultiChannelConnection<Object>() {
-                public Dispatch<Object> addOutgoingChannel(String channelKey) {
-                    throw new UnsupportedOperationException();
-                }
-
-                public void addIncomingChannel(String channelKey, Dispatch<Object> dispatch) {
-                    channels.put(channelKey, dispatch);
-                }
-
-                public void requestStop() {
-                    throw new UnsupportedOperationException();
-                }
-
-                public void stop() {
-                    throw new UnsupportedOperationException();
-                }
-
-                public Address getLocalAddress() {
-                    throw new UnsupportedOperationException();
-                }
-
-                public Address getRemoteAddress() {
-                    throw new UnsupportedOperationException();
-                }
-            };
-        }
-    }
-
-    public interface TestRemote {
-        void doStuff(String param);
-    }
-
-    public interface TestRemote2 {
-        void doStuff(String param);
-    }
-
-    public interface TestRemote3 extends TestRemote {
-    }
-}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnectorTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnectorTest.groovy
deleted file mode 100644
index efd77c8..0000000
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnectorTest.groovy
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package org.gradle.messaging.remote.internal
-
-import spock.lang.Specification
-import org.gradle.api.Action
-import java.util.concurrent.Executor
-import org.gradle.messaging.remote.ConnectEvent
-import org.gradle.messaging.remote.Address
-import org.gradle.messaging.remote.internal.protocol.ConnectRequest
-
-class HandshakeIncomingConnectorTest extends Specification {
-    private final Address localAddress = Mock()
-    private final Executor executor = Mock()
-    private final IncomingConnector target = Mock()
-    private final Connection connection = Mock()
-    private final HandshakeIncomingConnector connector = new HandshakeIncomingConnector(target, executor)
-
-    def acceptAllocatesAUriAndStartsListening() {
-        Action<ConnectEvent<Connection<Message>>> action = Mock()
-
-        when:
-        def address = connector.accept(action, false)
-
-        then:
-        address == new CompositeAddress(localAddress, 0L)
-        1 * target.accept(!null, false) >> localAddress
-        0 * target._
-    }
-
-    def eachCallToAcceptAllocatesADifferentAddress() {
-        Action<ConnectEvent<Connection<Message>>> action = Mock()
-
-        when:
-        def address1 = connector.accept(action, false)
-        def address2 = connector.accept(action, false)
-
-        then:
-        address1 == new CompositeAddress(localAddress, 0L)
-        address2 == new CompositeAddress(localAddress, 1L)
-        1 * target.accept(!null, false) >> localAddress
-        0 * target._
-    }
-    
-    def performsHandshakeOnAccept() {
-        Action<ConnectEvent<Connection<Message>>> action = Mock()
-        Address remoteAddress = Mock()
-        def wrappedAction
-        def handshakeRunnable
-        1 * target.accept(!null, false) >> { wrappedAction = it[0]; localAddress }
-
-        def address = connector.accept(action, false)
-
-        when:
-        wrappedAction.execute(new ConnectEvent<Connection<Message>>(connection, localAddress, remoteAddress))
-
-        then:
-        1 * executor.execute(!null) >> { handshakeRunnable = it[0] }
-
-        when:
-        handshakeRunnable.run()
-
-        then:
-        1 * connection.receive() >> new ConnectRequest(address)
-        1 * action.execute({ it instanceof ConnectEvent && it.connection == connection && it.localAddress == address})
-    }
-}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnectorTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnectorTest.groovy
deleted file mode 100644
index 9cd24cb..0000000
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnectorTest.groovy
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package org.gradle.messaging.remote.internal
-
-import spock.lang.Specification
-import org.gradle.messaging.remote.Address
-import org.gradle.messaging.remote.internal.protocol.ConnectRequest
-
-class HandshakeOutgoingConnectorTest extends Specification {
-    private final Address targetAddress = Mock()
-    private final OutgoingConnector<Message> target = Mock()
-    private final Connection<Message> connection = Mock()
-    private final HandshakeOutgoingConnector connector = new HandshakeOutgoingConnector(target)
-
-    def createsConnectionAndPerformsHandshake() {
-        def remoteAddress = new CompositeAddress(targetAddress, 0)
-
-        when:
-        def connection = connector.connect(remoteAddress)
-
-        then:
-        connection == this.connection
-        1 * target.connect(targetAddress) >> connection
-        1 * connection.dispatch({it instanceof ConnectRequest && it.destinationAddress == remoteAddress})
-    }
-
-    def stopsConnectionOnFailureToPerformHandshake() {
-        RuntimeException failure = new RuntimeException()
-
-        when:
-        connector.connect(new CompositeAddress(targetAddress, 0))
-
-        then:
-        def e = thrown(RuntimeException)
-        e == failure
-        1 * target.connect(targetAddress) >> connection
-        1 * connection.dispatch({it instanceof ConnectRequest}) >> { throw failure }
-        1 * connection.stop()
-    }
-
-    def failsWhenURIHasUnknownScheme() {
-        when:
-        connector.connect(targetAddress)
-
-        then:
-        def e = thrown(IllegalArgumentException)
-        e.message == "Cannot create a connection to address of unknown type: ${targetAddress}."
-    }
-}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy
index 2b59d94..3aa7263 100644
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy
@@ -165,6 +165,29 @@ class MessageTest extends Specification {
         transported.cause.stackTrace == cause.stackTrace
     }
 
+    def "creates placeholder with toString() behaviour as original"() {
+        def cause = new IOException("nested")
+        def broken = new SerializableToStringException("message", cause)
+
+        when:
+        def transported = transport(broken)
+        transported.toString()
+        then:
+
+        def toStringException = thrown(RuntimeException)
+        toStringException.getMessage() == "broken toString"
+
+        when:
+        cause = new IOException("nested")
+        broken = new UnserializableToStringException("message", cause)
+        transported = transport(broken)
+        transported.toString()
+
+        then:
+        toStringException = thrown(PlaceholderException)
+        toStringException.getMessage() == "broken toString"
+    }
+
     @Ignore
     @Issue("GRADLE-1996")
     def "can transport exception that implements writeReplace()"() {
@@ -211,6 +234,30 @@ class MessageTest extends Specification {
         }
     }
 
+    static class UnserializableToStringException extends RuntimeException {
+        UnserializableToStringException (String message, Throwable cause) {
+            super(message, cause)
+        }
+
+        public String toString() {
+            throw new UnserializableException("broken toString", null);
+        }
+
+        private void writeObject(ObjectOutputStream outstr) throws IOException {
+            outstr.writeObject(new Object())
+        }
+    }
+
+    static class SerializableToStringException extends RuntimeException {
+        SerializableToStringException(String message, Throwable cause) {
+            super(message, cause)
+        }
+
+        public String toString() {
+            throw new RuntimeException("broken toString", null);
+        }
+    }
+
     static class UndeserializableException extends RuntimeException {
         UndeserializableException(String message, Throwable cause) {
             super(message, cause)
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/PlaceholderExceptionTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/PlaceholderExceptionTest.groovy
index e8b3c95..315b7ff 100644
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/PlaceholderExceptionTest.groovy
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/PlaceholderExceptionTest.groovy
@@ -22,33 +22,21 @@ import spock.lang.Issue
 class PlaceholderExceptionTest extends Specification {
     def "toString() generally produces same output as original exception"() {
         def original = new Exception("original exception")
-        def placeholder = new PlaceholderException(original.getClass().name, original.message, original.cause)
+        def placeholder = new PlaceholderException(original.getClass().name, original.message, original.toString(), null, original.cause)
         
         expect:
         placeholder.toString() == original.toString()
     }
     
-    def "toString() doesn't produce same output as original exception if the latter overrides toString()"() {
+    def "toString() produces same output as original exception if the latter overrides toString()"() {
         def original = new Exception("original exception") {
             String toString() {
                 "fancy customized toString"
             }
         }
-        def placeholder = new PlaceholderException(original.getClass().name, original.message, original.cause)
-        
-        expect:
-        placeholder.toString() != original.toString()
-    }
-    
-    def "toString() doesn't produce same output as original exception if the latter has a localized message"() {
-        def original = new Exception("original exception") {
-            String getLocalizedMessage() {
-                "lokalisierte nachricht"
-            }
-        }
-        def placeholder = new PlaceholderException(original.getClass().name, original.message, original.cause)
+        def placeholder = new PlaceholderException(original.getClass().name, original.message, original.toString(), null, original.cause)
 
         expect:
-        placeholder.toString() != original.toString()
+        placeholder.toString() == original.toString()
     }
 }
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/ConnectionSetTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/ConnectionSetTest.groovy
new file mode 100644
index 0000000..f6d6ac3
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/ConnectionSetTest.groovy
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub
+
+import org.gradle.messaging.remote.internal.Connection
+import org.gradle.messaging.remote.internal.hub.protocol.ChannelIdentifier
+import org.gradle.messaging.remote.internal.hub.protocol.ChannelMessage
+import org.gradle.messaging.remote.internal.hub.protocol.ConnectionClosed
+import org.gradle.messaging.remote.internal.hub.protocol.ConnectionEstablished
+import org.gradle.messaging.remote.internal.hub.protocol.EndOfStream
+import org.gradle.messaging.remote.internal.hub.protocol.RejectedMessage
+import org.gradle.messaging.remote.internal.hub.queue.AbstractQueueTest
+
+class ConnectionSetTest extends AbstractQueueTest {
+    final IncomingQueue incomingQueue = new IncomingQueue(lock)
+    final OutgoingQueue outgoingQueue = new OutgoingQueue(incomingQueue, lock)
+    final ConnectionSet connections = new ConnectionSet(incomingQueue, outgoingQueue)
+
+    def "discards queued outgoing messages when stop requested and no connections"() {
+        def channel = new ChannelIdentifier("channel")
+        def outgoingMessage = new ChannelMessage(channel, "payload")
+
+        given:
+        def incoming = incomingQueue.getChannel(channel).newEndpoint()
+        outgoingQueue.dispatch(outgoingMessage)
+
+        when:
+        connections.requestStop()
+        def messages = []
+        incoming.take(messages)
+
+        then:
+        messages.size() == 2
+        messages[0] instanceof RejectedMessage
+        messages[0].payload == "payload"
+        messages[1] instanceof EndOfStream
+     }
+
+    def "does not discard queued outgoing messages when stop requested until all connections finished"() {
+        def channel = new ChannelIdentifier("channel")
+        def message = new ChannelMessage(channel, "payload")
+
+        given:
+        def incoming = incomingQueue.getChannel(channel).newEndpoint()
+        def connection = connections.add(Mock(Connection))
+        outgoingQueue.dispatch(message)
+
+        when:
+        connections.requestStop()
+        def messages = []
+        incoming.take(messages)
+
+        then:
+        messages.size() == 1
+        messages[0] instanceof ConnectionEstablished
+
+        when:
+        messages = []
+        connection.dispatchFinished()
+        connection.receiveFinished()
+        incoming.take(messages)
+
+        then:
+        messages.size() == 3
+        messages[0] instanceof ConnectionClosed
+        messages[1] instanceof RejectedMessage
+        messages[1].payload == "payload"
+        messages[2] instanceof EndOfStream
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/InterHubMessageSerializerTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/InterHubMessageSerializerTest.groovy
new file mode 100644
index 0000000..9966783
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/InterHubMessageSerializerTest.groovy
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub
+
+import org.gradle.messaging.remote.internal.hub.protocol.ChannelIdentifier
+import org.gradle.messaging.remote.internal.hub.protocol.ChannelMessage
+import org.gradle.messaging.remote.internal.hub.protocol.EndOfStream
+import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage
+import org.gradle.messaging.serialize.kryo.KryoSerializer
+import spock.lang.Specification
+
+class InterHubMessageSerializerTest extends Specification {
+    final InterHubMessageSerializer serializer = new InterHubMessageSerializer(new KryoSerializer<Object>())
+
+    def "can serialise ChannelMessage"() {
+        def channelId = new ChannelIdentifier("channel name")
+        def message = new ChannelMessage(channelId, "payload")
+
+        when:
+        def serialized = serialize(message)
+        def result = deserialize(serialized)
+
+        then:
+        result instanceof ChannelMessage
+        result.channel == channelId
+        result.payload == "payload"
+        serialized.length == 23
+    }
+
+    def "replaces a channel ID that has already been seen with an integer value"() {
+        def channelId = new ChannelIdentifier("channel name")
+        def message1 = new ChannelMessage(channelId, "payload 1")
+        def message2 = new ChannelMessage(channelId, "payload 2")
+
+        when:
+        def serialized = serialize(message1, message2)
+        def result = deserializeMultiple(serialized, 2)
+
+        then:
+        result[0] instanceof ChannelMessage
+        result[0].channel == channelId
+        result[0].payload == "payload 1"
+        result[1] instanceof ChannelMessage
+        result[1].channel == channelId
+        result[1].payload == "payload 2"
+        serialized.length == 38
+    }
+
+    def "can serialize messages for multiple channels"() {
+        def channelId1 = new ChannelIdentifier("channel 1")
+        def channelId2 = new ChannelIdentifier("channel 2")
+        def message1 = new ChannelMessage(channelId1, "payload 1")
+        def message2 = new ChannelMessage(channelId2, "payload 2")
+        def message3 = new ChannelMessage(channelId1, "payload 3")
+
+        when:
+        def serialized = serialize(message1, message2, message3)
+        def result = deserializeMultiple(serialized, 3)
+
+        then:
+        result[0] instanceof ChannelMessage
+        result[0].channel == channelId1
+        result[0].payload == "payload 1"
+        result[1] instanceof ChannelMessage
+        result[1].channel == channelId2
+        result[1].payload == "payload 2"
+        result[2] instanceof ChannelMessage
+        result[2].channel == channelId1
+        result[2].payload == "payload 3"
+        serialized.length == 57
+    }
+
+    def "can serialise EndOfStream"() {
+        when:
+        def serialized = serialize(new EndOfStream())
+        def result = deserialize(serialized)
+
+        then:
+        result instanceof EndOfStream
+        serialized.length == 1
+    }
+
+    def serialize(InterHubMessage... messages) {
+        def outStr = new ByteArrayOutputStream()
+        def writer = serializer.newWriter(outStr)
+        messages.each {
+            writer.write(it)
+        }
+        return outStr.toByteArray()
+    }
+
+    def deserialize(byte[] data) {
+        return serializer.newReader(new ByteArrayInputStream(data), null, null).read()
+    }
+
+    def deserializeMultiple(byte[] data, int count) {
+        def reader = serializer.newReader(new ByteArrayInputStream(data), null, null)
+        def result = []
+        count.times {
+            result << reader.read()
+        }
+        return result
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MessageHubBackedClientTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MessageHubBackedClientTest.groovy
new file mode 100644
index 0000000..d5ab039
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MessageHubBackedClientTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.messaging.remote.internal.hub
+
+import org.gradle.internal.concurrent.ExecutorFactory
+import org.gradle.internal.concurrent.StoppableExecutor
+import org.gradle.messaging.remote.Address
+import org.gradle.messaging.remote.internal.Connection
+import org.gradle.messaging.remote.internal.MessageSerializer
+import org.gradle.messaging.remote.internal.OutgoingConnector
+import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage
+import spock.lang.Specification
+
+class MessageHubBackedClientTest extends Specification {
+    final OutgoingConnector connector = Mock()
+    final ExecutorFactory executorFactory = Mock()
+    final MessageSerializer<InterHubMessage> serializer = Mock()
+    final MessageHubBackedClient client = new MessageHubBackedClient(connector, serializer, executorFactory)
+
+    def "creates connection and cleans up on stop"() {
+        Address address = Stub()
+        Connection<InterHubMessage> backingConnection = Mock()
+        StoppableExecutor executor = Mock()
+
+        when:
+        def objectConnection = client.getConnection(address)
+
+        then:
+        1 * connector.connect(address, serializer) >> backingConnection
+        1 * executorFactory.create("${backingConnection} workers") >> executor
+
+        when:
+        objectConnection.stop()
+
+        then:
+        1 * backingConnection.stop()
+        1 * executor.stop()
+        0 * _._
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MessageHubBackedServerTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MessageHubBackedServerTest.groovy
new file mode 100644
index 0000000..835131a
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MessageHubBackedServerTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.messaging.remote.internal.hub
+
+import org.gradle.api.Action
+import org.gradle.internal.concurrent.ExecutorFactory
+import org.gradle.internal.concurrent.StoppableExecutor
+import org.gradle.messaging.remote.Address
+import org.gradle.messaging.remote.ConnectEvent
+import org.gradle.messaging.remote.ConnectionAcceptor
+import org.gradle.messaging.remote.internal.Connection
+import org.gradle.messaging.remote.internal.IncomingConnector
+import org.gradle.messaging.remote.internal.MessageSerializer
+import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage
+import spock.lang.Specification
+
+class MessageHubBackedServerTest extends Specification {
+    final IncomingConnector connector = Mock()
+    final ExecutorFactory executorFactory = Mock()
+    final MessageSerializer<InterHubMessage> serializer = Mock()
+    final MessageHubBackedServer server = new MessageHubBackedServer(connector, serializer, executorFactory)
+
+    def "creates connection and cleans up on stop"() {
+        Address remoteAddress = Stub()
+        Address localAddress = Stub()
+        ConnectionAcceptor acceptor = Mock() {
+            getAddress() >> localAddress
+        }
+        Action<ConnectEvent<Connection<InterHubMessage>>> connectAction = Mock()
+        Connection<InterHubMessage> backingConnection = Mock()
+        StoppableExecutor executor = Mock()
+        def acceptAction
+        def connection
+
+        when:
+        server.accept(connectAction)
+
+        then:
+        1 * connector.accept(_, serializer, false) >> { acceptAction = it[0]; return acceptor }
+
+        when:
+        acceptAction.execute(new ConnectEvent<Connection<InterHubMessage>>(backingConnection, localAddress, remoteAddress))
+
+        then:
+        1 * executorFactory.create("${backingConnection} workers") >> executor
+        1 * connectAction.execute(_) >> { ConnectEvent event -> connection = event.connection }
+
+        when:
+        connection.stop()
+
+        then:
+        1 * backingConnection.stop()
+        1 * executor.stop()
+        0 * _._
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MessageHubTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MessageHubTest.groovy
new file mode 100644
index 0000000..64af625
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MessageHubTest.groovy
@@ -0,0 +1,680 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub
+
+import org.gradle.api.Action
+import org.gradle.messaging.dispatch.Dispatch
+import org.gradle.messaging.remote.internal.Connection
+import org.gradle.messaging.remote.internal.hub.protocol.ChannelIdentifier
+import org.gradle.messaging.remote.internal.hub.protocol.ChannelMessage
+import org.gradle.messaging.remote.internal.hub.protocol.EndOfStream
+import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage
+import org.gradle.test.fixtures.concurrent.ConcurrentSpec
+import spock.lang.Timeout
+
+import java.util.concurrent.BlockingQueue
+import java.util.concurrent.CopyOnWriteArrayList
+import java.util.concurrent.LinkedBlockingQueue
+
+ at Timeout(60)
+class MessageHubTest extends ConcurrentSpec {
+    final Action<Throwable> errorHandler = Mock()
+    final MessageHub hub = new MessageHub("<hub>", executorFactory, errorHandler)
+
+    def cleanup() {
+        hub.stop()
+    }
+
+    def "creates a separate dispatcher for each outgoing type"() {
+        when:
+        def dispatcher1 = hub.getOutgoing("channel1", String)
+        def dispatcher2 = hub.getOutgoing("channel1", Integer)
+
+        then:
+        dispatcher1 != null
+        dispatcher2 != null
+        dispatcher1 != dispatcher2
+    }
+
+    def "creates a separate dispatcher for each outgoing channel"() {
+        when:
+        def dispatcher1 = hub.getOutgoing("channel1", String)
+        def dispatcher2 = hub.getOutgoing("channel2", String)
+
+        then:
+        dispatcher1 != null
+        dispatcher2 != null
+        dispatcher1 != dispatcher2
+    }
+
+    def "can dispatch outgoing messages"() {
+        given:
+        def dispatcher = hub.getOutgoing("channel", String)
+
+        expect:
+        dispatcher.dispatch("message 1")
+        dispatcher.dispatch("message 2")
+    }
+
+    def "outgoing messages are dispatched asynchronously to connection"() {
+        Dispatch<InterHubMessage> outgoing = Mock()
+        def connection = new MockOutgoingConnection(outgoing)
+
+        given:
+        hub.addConnection(connection)
+
+        when:
+        operation.dispatch {
+            hub.getOutgoing("channel1", String).dispatch("message1")
+            hub.getOutgoing("channel1", String).dispatch("message2")
+            hub.getOutgoing("channel2", Long).dispatch(12)
+        }
+        thread.blockUntil.longDispatched
+
+        then:
+        1 * outgoing.dispatch({ it.payload == "message1" }) >> {
+            thread.blockUntil.dispatch
+            instant.message1Dispatched
+        }
+        1 * outgoing.dispatch({ it.payload == "message2" }) >> {
+            instant.message2Dispatched
+        }
+        1 * outgoing.dispatch({ it.payload == 12 }) >> {
+            instant.longDispatched
+        }
+        0 * _._
+
+        and:
+        operation.dispatch.end < instant.message1Dispatched
+        instant.message1Dispatched < instant.message2Dispatched
+        instant.message2Dispatched < instant.longDispatched
+
+        cleanup:
+        connection.stop()
+    }
+
+    def "queued outgoing messages are dispatched asynchronously to connection when connection is added"() {
+        Dispatch<InterHubMessage> outgoing = Mock()
+        def connection = new MockOutgoingConnection(outgoing)
+
+        given:
+        hub.getOutgoing("channel1", String).dispatch("message1")
+        hub.getOutgoing("channel1", String).dispatch("message2")
+        hub.getOutgoing("channel2", Long).dispatch(12)
+
+        when:
+        operation.connection {
+            hub.addConnection(connection)
+        }
+        thread.blockUntil.longDispatched
+
+        then:
+        1 * outgoing.dispatch({ it.payload == "message1" }) >> {
+            thread.blockUntil.connection
+            instant.message1Dispatched
+        }
+        1 * outgoing.dispatch({ it.payload == "message2" }) >> {
+            instant.message2Dispatched
+        }
+        1 * outgoing.dispatch({ it.payload == 12 }) >> {
+            instant.longDispatched
+        }
+        0 * _._
+
+        and:
+        operation.connection.end < instant.message1Dispatched
+        instant.message1Dispatched < instant.message2Dispatched
+        instant.message2Dispatched < instant.longDispatched
+
+        cleanup:
+        connection.stop()
+    }
+
+    def "stop blocks until all outgoing messages dispatched to connection"() {
+        Dispatch<InterHubMessage> outgoing = Mock()
+        def connection = new MockOutgoingConnection(outgoing)
+
+        when:
+        hub.addConnection(connection)
+        hub.getOutgoing("channel1", String).dispatch("message1")
+        hub.getOutgoing("channel1", String).dispatch("message2")
+        hub.getOutgoing("channel2", Long).dispatch(12)
+        hub.stop()
+
+        then:
+        1 * outgoing.dispatch({ it instanceof ChannelMessage && it.payload == "message1" })
+        1 * outgoing.dispatch({ it instanceof ChannelMessage && it.payload == "message2" })
+        1 * outgoing.dispatch({ it instanceof ChannelMessage && it.payload == 12 })
+        1 * outgoing.dispatch({ it instanceof EndOfStream}) >> { connection.stop() }
+        0 * _._
+    }
+
+    def "each outgoing message is dispatched in order to connection"() {
+        def messages = new CopyOnWriteArrayList()
+        Dispatch<InterHubMessage> outgoing = Mock()
+        def connection = new MockOutgoingConnection(outgoing)
+
+        given:
+        outgoing.dispatch({ it instanceof ChannelMessage }) >> { ChannelMessage message ->
+            messages.add(message.payload)
+            if (messages.size() == 20) {
+                instant.messagesReceived
+            }
+        }
+
+        and:
+        hub.addConnection(connection)
+
+        when:
+        def dispatcher = hub.getOutgoing("channel", Long)
+        20.times { dispatcher.dispatch(it) }
+        thread.blockUntil.messagesReceived
+
+        then:
+        messages == 0..19
+
+        cleanup:
+        connection.stop()
+    }
+
+    def "each outgoing message is dispatched to exactly one connection"() {
+        def messages = new CopyOnWriteArrayList()
+        Dispatch<InterHubMessage> outgoing = Mock()
+        def connection1 = new MockOutgoingConnection(outgoing)
+        def connection2 = new MockOutgoingConnection(outgoing)
+
+        given:
+        outgoing.dispatch({ it instanceof ChannelMessage }) >> { ChannelMessage message ->
+            messages.add(message.payload)
+            if (messages.size() == 20) {
+                instant.messagesReceived
+            }
+        }
+
+        and:
+        hub.addConnection(connection1)
+        hub.addConnection(connection2)
+
+        when:
+        def dispatcher = hub.getOutgoing("channel", Long)
+        20.times { dispatcher.dispatch(it) }
+        thread.blockUntil.messagesReceived
+
+        then:
+        new ArrayList<String>(messages).sort() == 0..19
+
+        cleanup:
+        connection1.stop()
+        connection2.stop()
+    }
+
+    def "stops dispatching outgoing messages to failed connection"() {
+        Dispatch<InterHubMessage> outgoing = Mock()
+        def connection = new MockOutgoingConnection(outgoing)
+        def dispatch = hub.getOutgoing("channel", String)
+        def failure = new RuntimeException()
+
+        given:
+        dispatch.dispatch("message 1")
+        dispatch.dispatch("message 2")
+
+        when:
+        hub.addConnection(connection)
+        thread.blockUntil.broken
+
+        then:
+        1 * outgoing.dispatch({ it.payload == "message 1" }) >> {
+            throw failure
+        }
+        1 * errorHandler.execute(failure) >> {
+            instant.broken
+        }
+        0 * _._
+
+        cleanup:
+        connection.stop()
+    }
+
+    def "incoming messages are dispatched asynchronously to handler"() {
+        def connection = new TestConnection()
+        Dispatch<String> handler = Mock()
+
+        given:
+        hub.addHandler("channel", handler)
+
+        when:
+        hub.addConnection(connection)
+        connection.queueIncoming(new ChannelMessage(new ChannelIdentifier("channel"), "message 1"))
+        thread.blockUntil.message1Received
+        connection.queueIncoming(new ChannelMessage(new ChannelIdentifier("channel"), "message 2"))
+        connection.stop()
+        thread.blockUntil.message2Received
+
+        then:
+        1 * handler.dispatch("message 1") >> {
+            instant.message1Received
+            thread.block()
+        }
+        1 * handler.dispatch("message 2") >> {
+            instant.message2Received
+        }
+        0 * _._
+
+        and:
+        instant.message1Received < instant.message2Received
+    }
+
+    def "queued incoming messages are dispatched when handler added"() {
+        def connection = new TestConnection()
+        Dispatch<String> handler = Mock()
+
+        given:
+        connection.queueIncoming(new ChannelMessage(new ChannelIdentifier("channel"), "message 1"))
+        connection.queueIncoming(new ChannelMessage(new ChannelIdentifier("channel"), "message 2"))
+        hub.addConnection(connection)
+
+        when:
+        hub.addHandler("channel", handler)
+        thread.blockUntil.message2Received
+
+        then:
+        1 * handler.dispatch("message 1") >> {
+            instant.message1Received
+        }
+        1 * handler.dispatch("message 2") >> {
+            instant.message2Received
+        }
+        0 * _._
+
+        and:
+        instant.message1Received < instant.message2Received
+
+        cleanup:
+        connection.stop()
+    }
+
+    def "each incoming message is dispatched to exactly one handler"() {
+        def connection = new TestConnection()
+        def messages = new CopyOnWriteArrayList()
+        Dispatch<Long> handler1 = Mock()
+        Dispatch<Long> handler2 = Mock()
+
+        given:
+        handler1.dispatch(_) >> { messages << it[0] }
+        handler2.dispatch(_) >> { messages << it[0] }
+        hub.addHandler("channel", handler1)
+        hub.addHandler("channel", handler2)
+
+        when:
+        hub.addConnection(connection)
+        20.times { connection.queueIncoming(new ChannelMessage(new ChannelIdentifier("channel"), it)) }
+        connection.stop()
+        hub.stop()
+
+        then:
+        messages as Set == (0..19) as Set
+    }
+
+    def "incoming messages are dispatched to handler for the appropriate channel"() {
+        def connection = new TestConnection()
+        Dispatch<String> handler1 = Mock()
+        Dispatch<String> handler2 = Mock()
+
+        given:
+        hub.addHandler("channel 1", handler1)
+        hub.addHandler("channel 2", handler2)
+        hub.addConnection(connection)
+
+        when:
+        connection.queueIncoming(new ChannelMessage(new ChannelIdentifier("channel 1"), "message 1"))
+        connection.queueIncoming(new ChannelMessage(new ChannelIdentifier("channel 2"), "message 2"))
+        connection.queueIncoming(new ChannelMessage(new ChannelIdentifier("channel 1"), "message 3"))
+        thread.blockUntil.channel1Received
+        thread.blockUntil.channel2Received
+
+        then:
+        1 * handler1.dispatch("message 1")
+        1 * handler1.dispatch("message 3") >> { instant.channel1Received }
+        1 * handler2.dispatch("message 2") >> { instant.channel2Received }
+        0 * _._
+
+        cleanup:
+        connection.stop()
+    }
+
+    def "stops dispatching to failed handler"() {
+        def connection = new TestConnection()
+        def failure = new RuntimeException()
+        Dispatch<String> handler = Mock()
+
+        given:
+        hub.addHandler("channel", handler)
+        hub.addConnection(connection)
+
+        when:
+        connection.queueIncoming(new ChannelMessage(new ChannelIdentifier("channel"), "message 1"))
+        connection.queueIncoming(new ChannelMessage(new ChannelIdentifier("channel"), "message 2"))
+        connection.queueIncoming(new ChannelMessage(new ChannelIdentifier("channel"), "message 3"))
+        thread.blockUntil.failed
+
+        then:
+        1 * handler.dispatch("message 1") >> { throw failure }
+        1 * errorHandler.execute(failure) >> {
+            instant.failed
+        }
+        0 * _._
+
+        cleanup:
+        connection.stop()
+    }
+
+    def "stop blocks until queued incoming messages handled"() {
+        def connection = new TestConnection()
+        Dispatch<String> handler = Mock()
+
+        given:
+        hub.addHandler("channel", handler)
+        hub.addConnection(connection)
+
+        when:
+        connection.queueIncoming(new ChannelMessage(new ChannelIdentifier("channel"), "message 1"))
+        connection.queueIncoming(new ChannelMessage(new ChannelIdentifier("channel"), "message 2"))
+        connection.queueIncoming(new ChannelMessage(new ChannelIdentifier("channel"), "message 3"))
+        connection.stop()
+        hub.stop()
+
+        then:
+        1 * handler.dispatch("message 1") >> { thread.block() }
+        1 * handler.dispatch("message 2")
+        1 * handler.dispatch("message 3")
+        0 * _._
+    }
+
+    def "queued outgoing messages are dispatched asynchronously to rejected message listener when stop requested and no connection available"() {
+        RejectedMessageListener listener = Mock()
+
+        given:
+        hub.addHandler("channel", listener)
+        def dispatcher = hub.getOutgoing("channel", String)
+        dispatcher.dispatch("message 1")
+        dispatcher.dispatch("message 2")
+
+        when:
+        operation.requestStop {
+            hub.requestStop()
+        }
+        hub.stop()
+
+        then:
+        1 * listener.messageDiscarded("message 1") >> {
+            thread.block()
+            instant.message1Handled
+        }
+        1 * listener.messageDiscarded("message 2") >> {
+            instant.message2Handled
+        }
+        0 * _._
+
+        and:
+        operation.requestStop.end < instant.message1Handled
+        instant.message1Handled < instant.message2Handled
+    }
+
+    def "rejected message listener is not notified when no queued outgoing messages when stop requested"() {
+        RejectedMessageListener listener = Mock()
+
+        given:
+        hub.addHandler("channel", listener)
+
+        when:
+        hub.stop()
+
+        then:
+        0 * _._
+    }
+
+    def "rejected message listener is notified only of rejected outgoing messages on associated channel"() {
+        RejectedMessageListener listener = Mock()
+
+        given:
+        hub.addHandler("channel", listener)
+        hub.getOutgoing("other", String).dispatch("ignore me")
+        hub.getOutgoing("channel", String).dispatch("discarded")
+
+        when:
+        hub.stop()
+
+        then:
+        1 * listener.messageDiscarded("discarded")
+        0 * _._
+    }
+
+    def "stops dispatching rejected outgoing messages to failed listener"() {
+        RejectedMessageListener listener = Mock()
+        def dispatch = hub.getOutgoing("channel", String)
+        def failure = new RuntimeException()
+
+        given:
+        dispatch.dispatch("message 1")
+        dispatch.dispatch("message 2")
+        hub.addHandler("channel", listener)
+
+        when:
+        hub.stop()
+
+        then:
+        1 * listener.messageDiscarded("message 1") >> {
+            throw failure
+        }
+        1 * errorHandler.execute(failure)
+        0 * _._
+    }
+
+    def "only handlers that implement RejectedMessageListener are notified of rejected outgoing messages"() {
+        RejectedMessageListener listener1 = Mock()
+        RejectedMessageListener listener2 = Mock()
+
+        given:
+        hub.addHandler("channel", listener1)
+        hub.addHandler("channel", listener2)
+        hub.addHandler("channel", new Object())
+        hub.getOutgoing("channel", String).dispatch("discarded")
+
+        when:
+        hub.stop()
+
+        then:
+        1 * listener1.messageDiscarded("discarded")
+        1 * listener2.messageDiscarded("discarded")
+        0 * _._
+    }
+
+    def "notifies HubStateListener asynchronously when connection added and when end-of-stream reached for each connection"() {
+        HubStateListener listener1 = Mock()
+        HubStateListener listener2 = Mock()
+        def connection1 = new TestConnection()
+        def connection2 = new TestConnection()
+
+        given:
+        hub.addHandler("channel", listener1)
+
+        when:
+        hub.addConnection(connection1)
+        hub.addConnection(connection2)
+        hub.addHandler("channel", listener2)
+        thread.blockUntil.listener1Connect
+        thread.blockUntil.listener2Connect
+
+        then:
+        1 * listener1.onConnect()
+        1 * listener1.onConnect() >> { instant.listener1Connect }
+        1 * listener2.onConnect()
+        1 * listener2.onConnect() >> { instant.listener2Connect }
+        0 * _._
+
+        when:
+        connection1.stop()
+        thread.blockUntil.listener1Disconnect
+        thread.blockUntil.listener2Disconnect
+
+        then:
+        1 * listener1.onDisconnect() >> { instant.listener1Disconnect }
+        1 * listener2.onDisconnect() >> { instant.listener2Disconnect }
+        0 * _._
+
+        cleanup:
+        connection1.stop()
+        connection2.stop()
+    }
+
+    def "stop blocks until hub state listeners notified"() {
+        HubStateListener listener = Mock()
+        def connection1 = new TestConnection()
+        def connection2 = new TestConnection()
+
+        given:
+        hub.addHandler("channel", listener)
+
+        when:
+        hub.addConnection(connection1)
+        hub.addConnection(connection2)
+        connection1.stop()
+        connection2.stop()
+        operation.stop {
+            hub.stop()
+        }
+
+        then:
+        2 * listener.onConnect()
+        1 * listener.onDisconnect()
+        1 * listener.onDisconnect() >> {
+            thread.block()
+            instant.listenerNotified
+        }
+        0 * _._
+
+        and:
+        operation.stop.end > instant.listenerNotified
+    }
+
+    def "cannot dispatch outgoing messages after stop requested"() {
+        given:
+        def dispatcher = hub.getOutgoing("channel", String)
+
+        when:
+        hub.requestStop()
+        dispatcher.dispatch("message 1")
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot dispatch message, as <hub> has been stopped.'
+    }
+
+    def "cannot create dispatcher after stop started"() {
+        given:
+        hub.getOutgoing("channel", String)
+
+        when:
+        hub.requestStop()
+        hub.getOutgoing("channel", String)
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot create outgoing dispatch, as <hub> has been stopped.'
+    }
+
+    def "cannot add handler after stop started"() {
+        when:
+        hub.requestStop()
+        hub.addHandler("channel", new Object())
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot add handler, as <hub> has been stopped.'
+    }
+
+    def "cannot add connection after stop started"() {
+        when:
+        hub.requestStop()
+        hub.addConnection(Mock(Connection))
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot add connection, as <hub> has been stopped.'
+    }
+
+    def "stop and request stop do nothing when already stopped"() {
+        when:
+        hub.stop()
+        hub.stop()
+        hub.requestStop()
+
+        then:
+        0 * _._
+    }
+
+    private static class MockOutgoingConnection implements Connection<InterHubMessage> {
+        private final Dispatch<InterHubMessage> dispatch
+        private final BlockingQueue<InterHubMessage> incoming = new LinkedBlockingQueue<>()
+
+        MockOutgoingConnection(Dispatch<InterHubMessage> dispatch) {
+            this.dispatch = dispatch
+        }
+
+        void dispatch(InterHubMessage message) {
+            dispatch.dispatch(message)
+        }
+
+        InterHubMessage receive() {
+            return incoming.take()
+        }
+
+        void requestStop() {
+            throw new UnsupportedOperationException()
+        }
+
+        void stop() {
+            incoming.put(new EndOfStream())
+        }
+    }
+
+    private static class TestConnection implements Connection<InterHubMessage> {
+        private final BlockingQueue<InterHubMessage> incoming = new LinkedBlockingQueue<>()
+        private final BlockingQueue<InterHubMessage> outgoing = new LinkedBlockingQueue<>()
+
+        void dispatch(InterHubMessage message) {
+            outgoing.put(message)
+        }
+
+        InterHubMessage receive() {
+            def message = incoming.take()
+            return message instanceof EndOfStream ? null : message
+        }
+
+        void queueIncoming(InterHubMessage message) {
+            incoming.put(message)
+        }
+
+        void requestStop() {
+            throw new UnsupportedOperationException()
+        }
+
+        void stop() {
+            incoming.put(new EndOfStream())
+        }
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MethodInvocationSerializerTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MethodInvocationSerializerTest.groovy
new file mode 100644
index 0000000..c99d5d5
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/MethodInvocationSerializerTest.groovy
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub
+
+import com.esotericsoftware.kryo.io.Input
+import com.esotericsoftware.kryo.io.Output
+import org.gradle.messaging.dispatch.MethodInvocation
+import org.gradle.messaging.serialize.kryo.KryoSerializer
+import spock.lang.Specification
+
+class MethodInvocationSerializerTest extends Specification {
+    final classLoader = new GroovyClassLoader(getClass().classLoader)
+    final serializer = new MethodInvocationSerializer(classLoader, new KryoSerializer<Object[]>())
+
+    def "serializes a method invocation with parameters"() {
+        def method = String.class.getMethod("substring", Integer.TYPE, Integer.TYPE)
+        def invocation = new MethodInvocation(method, [1, 2] as Object[])
+
+        when:
+        def serialized = serialize(invocation)
+        def result = deserialize(serialized)
+
+        then:
+        result.method == method
+        result.arguments == [1, 2] as Object[]
+        serialized.length == 60
+    }
+
+    def "replaces a method that has already been seen with an integer ID"() {
+        def method1 = String.class.getMethod("substring", Integer.TYPE, Integer.TYPE)
+        def method2 = String.class.getMethod("substring", Integer.TYPE, Integer.TYPE)
+        def invocation1 = new MethodInvocation(method1, [1, 2] as Object[])
+        def invocation2 = new MethodInvocation(method2, [3, 4] as Object[])
+
+        when:
+        def serialized = serialize(invocation1, invocation2)
+        def result = deserializeMultiple(serialized, 2)
+
+        then:
+        result[0].method == method1
+        result[0].arguments == [1, 2] as Object[]
+        result[1].method == method1
+        result[1].arguments == [3, 4] as Object[]
+        serialized.length == 88
+    }
+
+    def "serializes method invocations for multiple methods"() {
+        def method1 = String.class.getMethod("substring", Integer.TYPE, Integer.TYPE)
+        def method2 = String.class.getMethod("substring", Integer.TYPE)
+        def invocation1 = new MethodInvocation(method1, [1, 2] as Object[])
+        def invocation2 = new MethodInvocation(method2, [3] as Object[])
+        def invocation3 = new MethodInvocation(method1, [4, 5] as Object[])
+
+        when:
+        def serialized = serialize(invocation1, invocation2, invocation3)
+        def result = deserializeMultiple(serialized, 3)
+
+        then:
+        result[0].method == method1
+        result[0].arguments == [1, 2] as Object[]
+        result[1].method == method2
+        result[1].arguments == [3] as Object[]
+        result[2].method == method1
+        result[2].arguments == [4, 5] as Object[]
+    }
+
+    def "uses provided ClassLoader to locate incoming method invocation"() {
+        Class cl = classLoader.parseClass('package org.gradle.test; class TestObj { void doStuff() { } }')
+        def method = cl.getMethod("doStuff")
+        def invocation = new MethodInvocation(method, [] as Object[])
+
+        when:
+        def serialized = serialize(invocation)
+        def result = deserialize(serialized)
+
+        then:
+        result.method == method
+        result.arguments == [] as Object[]
+    }
+
+    def serialize(MethodInvocation... messages) {
+        def outStr = new ByteArrayOutputStream()
+        def output = new Output(outStr)
+        def writer = serializer.newWriter(output)
+        messages.each {
+            writer.write(it)
+        }
+        output.flush()
+        return outStr.toByteArray()
+    }
+
+    def deserialize(byte[] data) {
+        return serializer.newReader(new Input(data)).read()
+    }
+
+    def deserializeMultiple(byte[] data, int count) {
+        def reader = serializer.newReader(new Input(data))
+        def result = []
+        count.times {
+            result << reader.read()
+        }
+        return result
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/protocol/ChannelIdentifierTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/protocol/ChannelIdentifierTest.groovy
new file mode 100644
index 0000000..bd55195
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/protocol/ChannelIdentifierTest.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub.protocol
+
+import spock.lang.Specification
+
+class ChannelIdentifierTest extends Specification {
+    def "equals and hash code"() {
+        def id = new ChannelIdentifier("channel")
+        def same = new ChannelIdentifier("channel")
+        def different = new ChannelIdentifier("other")
+
+        expect:
+        id == same
+        id.hashCode() == same.hashCode()
+        id != different
+        id != null
+        id != "channel"
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/queue/AbstractQueueTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/queue/AbstractQueueTest.groovy
new file mode 100644
index 0000000..f5f19c3
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/queue/AbstractQueueTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub.queue
+
+import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage
+import spock.lang.Specification
+
+import java.util.concurrent.locks.Condition
+import java.util.concurrent.locks.Lock
+
+abstract class AbstractQueueTest extends Specification {
+    final Condition broken = Stub() {
+        await() >> { throw new UnsupportedOperationException("should not be waiting") }
+    }
+    final Lock lock = Stub() {
+        newCondition() >> broken
+    }
+
+    def unicast() {
+        InterHubMessage message = Stub() {
+            getDelivery() >> InterHubMessage.Delivery.SingleHandler
+        }
+        return message
+    }
+
+    def broadcast() {
+        InterHubMessage message = Stub() {
+            getDelivery() >> InterHubMessage.Delivery.AllHandlers
+        }
+        return message
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/queue/MultiChannelQueueTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/queue/MultiChannelQueueTest.groovy
new file mode 100644
index 0000000..a0deb9c
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/queue/MultiChannelQueueTest.groovy
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub.queue
+
+import org.gradle.messaging.remote.internal.Connection
+import org.gradle.messaging.remote.internal.hub.protocol.ChannelIdentifier
+import org.gradle.messaging.remote.internal.hub.protocol.ChannelMessage
+import org.gradle.messaging.remote.internal.hub.protocol.ConnectionEstablished
+
+class MultiChannelQueueTest extends AbstractQueueTest {
+    final MultiChannelQueue queue = new MultiChannelQueue(lock)
+
+    def "adds and caches channel queue on first lookup"() {
+        def id1 = new ChannelIdentifier("channel1")
+        def id2 = new ChannelIdentifier("channel2")
+
+        expect:
+        def channelQueue = queue.getChannel(id1)
+        channelQueue == queue.getChannel(id1)
+        channelQueue != queue.getChannel(id2)
+    }
+
+    def "adds channel queue when channel message added"() {
+        def id1 = new ChannelIdentifier("channel1")
+        def id2 = new ChannelIdentifier("channel2")
+        def message1 = new ChannelMessage(id1, "message 1")
+        def message2 = new ChannelMessage(id2, "message 2")
+        def message3 = new ChannelMessage(id1, "message 3")
+
+        given:
+        queue.queue(message1)
+        queue.queue(message2)
+        queue.queue(message3)
+
+        when:
+        def messages1 = []
+        def messages2 = []
+        queue.getChannel(id1).newEndpoint().take(messages1)
+        queue.getChannel(id2).newEndpoint().take(messages2)
+
+        then:
+        messages1 == [message1, message3]
+        messages2 == [message2]
+    }
+
+    def "forwards channel message to channel queue"() {
+        def id1 = new ChannelIdentifier("channel1")
+        def id2 = new ChannelIdentifier("channel2")
+        def message1 = new ChannelMessage(id1, "message 1")
+        def message2 = new ChannelMessage(id2, "message 2")
+        def message3 = new ChannelMessage(id1, "message 3")
+
+        given:
+        def endpoint1 = queue.getChannel(id1).newEndpoint()
+        def endpoint2 = queue.getChannel(id2).newEndpoint()
+
+        when:
+        def messages1 = []
+        def messages2 = []
+        queue.queue(message1)
+        queue.queue(message2)
+        queue.queue(message3)
+        endpoint1.take(messages1)
+        endpoint2.take(messages2)
+
+        then:
+        messages1 == [message1, message3]
+        messages2 == [message2]
+    }
+
+    def "forwards broadcast message to all channel queues"() {
+        def id1 = new ChannelIdentifier("channel1")
+        def id2 = new ChannelIdentifier("channel2")
+        def message1 = new ChannelMessage(id1, "message 1")
+        def message2 = broadcast()
+
+        given:
+        def endpoint1 = queue.getChannel(id1).newEndpoint()
+        def endpoint2 = queue.getChannel(id2).newEndpoint()
+
+        when:
+        def messages1 = []
+        def messages2 = []
+        queue.queue(message1)
+        queue.queue(message2)
+        endpoint1.take(messages1)
+        endpoint2.take(messages2)
+
+        then:
+        messages1 == [message1, message2]
+        messages2 == [message2]
+    }
+
+    def "forwards most recent stateful broadcast messages to all new queues"() {
+        def id1 = new ChannelIdentifier("channel1")
+        def message1 = new ConnectionEstablished(Mock(Connection))
+        def message2 = new ChannelMessage(id1, "message 2")
+        def message3 = new ConnectionEstablished(Mock(Connection))
+
+        given:
+        queue.queue(message1)
+        queue.queue(message2)
+
+        when:
+        def endpoint1 = queue.getChannel(id1).newEndpoint()
+        queue.queue(message3)
+        def endpoint2 = queue.getChannel(id1).newEndpoint()
+        def messages1 = []
+        def messages2 = []
+        endpoint1.take(messages1)
+        endpoint1.take(messages1)
+        endpoint2.take(messages2)
+
+        then:
+        messages1 == [message1, message2, message3]
+        messages2 == [message1, message3]
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/queue/MultiEndPointQueueTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/queue/MultiEndPointQueueTest.groovy
new file mode 100644
index 0000000..e3d7a25
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/queue/MultiEndPointQueueTest.groovy
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub.queue
+
+import org.gradle.messaging.remote.internal.hub.protocol.EndOfStream
+
+class MultiEndPointQueueTest extends AbstractQueueTest {
+    final MultiEndPointQueue queue = new MultiEndPointQueue(lock)
+
+    def "forwards queued unicast messages to first waiting endpoint"() {
+        given:
+        def message1 = unicast()
+        def message2 = unicast()
+        def endpoint = queue.newEndpoint()
+
+        and:
+        queue.dispatch(message1)
+        queue.dispatch(message2)
+
+        when:
+        queue.empty(endpoint)
+        def messages = []
+        endpoint.take(messages)
+
+        then:
+        messages == [message1, message2]
+    }
+
+    def "forwards unicast message to first waiting endpoint"() {
+        given:
+        def message = unicast()
+        def endpoint = queue.newEndpoint()
+
+        and:
+        queue.empty(endpoint)
+
+        when:
+        queue.dispatch(message)
+        def messages = []
+        endpoint.take(messages)
+
+        then:
+        messages == [message]
+    }
+
+    def "forwards queued broadcast messages to all endpoints"() {
+        given:
+        def message1 = unicast()
+        def message2 = broadcast()
+        def message3 = unicast()
+        def endpoint1 = queue.newEndpoint()
+        def endpoint2 = queue.newEndpoint()
+
+        and:
+        queue.dispatch(message1)
+        queue.dispatch(message2)
+        queue.dispatch(message3)
+
+        when:
+        queue.empty(endpoint1)
+        def messages1 = []
+        endpoint1.take(messages1)
+        def messages2 = []
+        endpoint2.take(messages2)
+
+        then:
+        messages1 == [message1, message2, message3]
+        messages2 == [message2]
+    }
+
+    def "forwards broadcast messages to all endpoints when nothing queued and nothing waiting"() {
+        given:
+        def message = broadcast()
+        def message2 = unicast()
+        def endpoint1 = queue.newEndpoint()
+        def endpoint2 = queue.newEndpoint()
+
+        when:
+        queue.dispatch(message)
+        queue.dispatch(message2)
+        def messages1 = []
+        endpoint1.take(messages1)
+        def messages2 = []
+        endpoint2.take(messages2)
+
+        then:
+        messages1 == [message]
+        messages2 == [message]
+    }
+
+    def "forwards broadcast messages to all endpoints when nothing queued"() {
+        given:
+        def message = broadcast()
+        def message2 = unicast()
+        def endpoint1 = queue.newEndpoint()
+        def endpoint2 = queue.newEndpoint()
+
+        and:
+        queue.empty(endpoint1)
+        queue.empty(endpoint2)
+
+        when:
+        queue.dispatch(message)
+        queue.dispatch(message2)
+        def messages1 = []
+        endpoint1.take(messages1)
+        def messages2 = []
+        endpoint2.take(messages2)
+
+        then:
+        messages1 == [message]
+        messages2 == [message]
+    }
+
+    def "buffers messages when there are no endpoints"() {
+        given:
+        def message1 = broadcast()
+        def message2 = unicast()
+        def message3 = unicast()
+
+        and:
+        queue.dispatch(message1)
+        queue.dispatch(message2)
+        queue.dispatch(message3)
+
+        when:
+        def endpoint = queue.newEndpoint()
+        def messages = []
+        endpoint.take(messages)
+
+        then:
+        messages == [message1, message2, message3]
+    }
+
+    def "does not dispatch anything to endpoint that has stopped"() {
+        given:
+        def endpoint = queue.newEndpoint()
+        queue.empty(endpoint)
+
+        when:
+        endpoint.stop()
+        queue.dispatch(broadcast())
+        queue.dispatch(unicast())
+        def messages = []
+        endpoint.take(messages)
+
+        then:
+        messages.size() == 1
+        messages[0] instanceof EndOfStream
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/queue/QueueInitializerTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/queue/QueueInitializerTest.groovy
new file mode 100644
index 0000000..742ab2c
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/hub/queue/QueueInitializerTest.groovy
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.messaging.remote.internal.hub.queue
+
+import org.gradle.messaging.dispatch.Dispatch
+import org.gradle.messaging.remote.internal.Connection
+import org.gradle.messaging.remote.internal.hub.protocol.ConnectionClosed
+import org.gradle.messaging.remote.internal.hub.protocol.ConnectionEstablished
+import org.gradle.messaging.remote.internal.hub.protocol.EndOfStream
+import org.gradle.messaging.remote.internal.hub.protocol.InterHubMessage
+
+class QueueInitializerTest extends AbstractQueueTest {
+    final QueueInitializer initializer = new QueueInitializer()
+    final Dispatch<InterHubMessage> queue = Mock()
+
+    def "does nothing when no stateful messages received"() {
+        when:
+        initializer.onQueueAdded(queue)
+
+        then:
+        0 * queue._
+    }
+
+    def "initialises queue with any ConnectionEstablished messages received"() {
+        given:
+        def message1 = new ConnectionEstablished(Mock(Connection))
+        def message2 = new ConnectionEstablished(Mock(Connection))
+
+        initializer.onStatefulMessage(message1)
+        initializer.onStatefulMessage(message2)
+
+        when:
+        initializer.onQueueAdded(queue)
+
+        then:
+        1 * queue.dispatch(message1)
+        1 * queue.dispatch(message2)
+        0 * queue._
+    }
+
+    def "discards ConnectionEstablished message for which a corresponding ConnectionClosed message received"() {
+        given:
+        def message1 = new ConnectionEstablished(Mock(Connection))
+        def message2 = new ConnectionEstablished(Mock(Connection))
+
+        initializer.onStatefulMessage(message1)
+        initializer.onStatefulMessage(message2)
+        initializer.onStatefulMessage(new ConnectionClosed(message2.connection))
+
+        when:
+        initializer.onQueueAdded(queue)
+
+        then:
+        1 * queue.dispatch(message1)
+        0 * queue._
+    }
+
+    def "discards message on end of stream received"() {
+        given:
+        def closed = new EndOfStream()
+
+        initializer.onStatefulMessage(new ConnectionEstablished(Mock(Connection)))
+        initializer.onStatefulMessage(new ConnectionEstablished(Mock(Connection)))
+        initializer.onStatefulMessage(closed)
+
+        when:
+        initializer.onQueueAdded(queue)
+
+        then:
+        1 * queue.dispatch(closed)
+        0 * queue._
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorConcurrencyTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorConcurrencyTest.groovy
index b88f495..22f385e 100644
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorConcurrencyTest.groovy
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorConcurrencyTest.groovy
@@ -31,8 +31,8 @@ class TcpConnectorConcurrencyTest extends ConcurrentSpecification {
 
     //sharing serializer adds extra flavor...
     final serializer = new DefaultMessageSerializer<Object>(getClass().classLoader)
-    final outgoingConnector = new TcpOutgoingConnector<Object>(serializer)
-    final incomingConnector = new TcpIncomingConnector<Object>(executorFactory, serializer, new InetAddressFactory(), new UUIDGenerator())
+    final outgoingConnector = new TcpOutgoingConnector()
+    final incomingConnector = new TcpIncomingConnector(executorFactory, new InetAddressFactory(), new UUIDGenerator())
 
     @Timeout(60)
     @Ignore
@@ -55,8 +55,8 @@ class TcpConnectorConcurrencyTest extends ConcurrentSpecification {
             }
         }
 
-        def address = incomingConnector.accept(action, false)
-        def connection = outgoingConnector.connect(address)
+        def address = incomingConnector.accept(action, getClass().classLoader, false)
+        def connection = outgoingConnector.connect(address, getClass().classLoader)
 
         when:
         def all = []
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorTest.groovy
index 2157d73..c18d67c 100644
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorTest.groovy
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorTest.groovy
@@ -21,102 +21,164 @@ import org.gradle.messaging.remote.ConnectEvent
 import org.gradle.messaging.remote.internal.ConnectException
 import org.gradle.messaging.remote.internal.Connection
 import org.gradle.messaging.remote.internal.DefaultMessageSerializer
-import org.gradle.util.ConcurrentSpecification
+import org.gradle.test.fixtures.concurrent.ConcurrentSpec
 
-import java.util.concurrent.CountDownLatch
-
-class TcpConnectorTest extends ConcurrentSpecification {
+class TcpConnectorTest extends ConcurrentSpec {
     final def serializer = new DefaultMessageSerializer<String>(getClass().classLoader)
     final def idGenerator = new UUIDGenerator()
     final def addressFactory = new InetAddressFactory()
-    final def outgoingConnector = new TcpOutgoingConnector<String>(serializer)
-    final def incomingConnector = new TcpIncomingConnector<String>(executorFactory, serializer, addressFactory, idGenerator)
+    final def outgoingConnector = new TcpOutgoingConnector()
+    final def incomingConnector = new TcpIncomingConnector(executorFactory, addressFactory, idGenerator)
 
     def "client can connect to server"() {
         Action action = Mock()
 
         when:
-        def address = incomingConnector.accept(action, false)
-        def connection = outgoingConnector.connect(address)
+        def acceptor = incomingConnector.accept(action, serializer, false)
+        def connection = outgoingConnector.connect(acceptor.address, serializer)
 
         then:
         connection != null
 
         cleanup:
-        incomingConnector.requestStop()
+        acceptor?.requestStop()
     }
 
     def "client can connect to server using remote addresses"() {
         Action action = Mock()
 
         when:
-        def address = incomingConnector.accept(action, true)
-        def connection = outgoingConnector.connect(address)
+        def acceptor = incomingConnector.accept(action, serializer, true)
+        def connection = outgoingConnector.connect(acceptor.address, serializer)
 
         then:
         connection != null
 
         cleanup:
-        incomingConnector.requestStop()
+        acceptor?.requestStop()
     }
 
     def "server executes action when incoming connection received"() {
-        def connectionReceived = startsAsyncAction()
         Action action = Mock()
 
         when:
-        connectionReceived.started {
-            def address = incomingConnector.accept(action, false)
-            outgoingConnector.connect(address)
-        }
+        def acceptor = incomingConnector.accept(action, serializer, false)
+        outgoingConnector.connect(acceptor.address, serializer)
+        thread.blockUntil.connected
 
         then:
-        1 * action.execute(!null) >> { connectionReceived.done() }
+        1 * action.execute(!null) >> { instant.connected }
 
         cleanup:
-        incomingConnector.requestStop()
+        acceptor?.requestStop()
     }
 
     def "client throws exception when cannot connect to server"() {
         def address = new MultiChoiceAddress("address", 12345, [InetAddress.getByName("localhost")])
 
         when:
-        outgoingConnector.connect(address)
+        outgoingConnector.connect(address, serializer)
 
         then:
         ConnectException e = thrown()
         e.message.startsWith "Could not connect to server ${address}."
+        e.cause instanceof java.net.ConnectException
+    }
+
+    def "the exception includes last failure when cannot connect"() {
+        def address = new MultiChoiceAddress("address", 12345, [InetAddress.getByName("localhost"), InetAddress.getByName("127.0.0.1")])
+
+        when:
+        outgoingConnector.connect(address, serializer)
+
+        then:
+        ConnectException e = thrown()
+        e.message.startsWith "Could not connect to server ${address}."
+        e.cause instanceof java.net.ConnectException
+    }
+
+    def "client cannot connect when server has requested stop"() {
+        when:
+        def acceptor = incomingConnector.accept(Mock(Action), serializer, false)
+        acceptor.requestStop()
+        outgoingConnector.connect(acceptor.address, serializer)
+
+        then:
+        ConnectException e = thrown()
+        e.message.startsWith "Could not connect to server ${acceptor.address}."
+        e.cause instanceof java.net.ConnectException
+    }
+
+    def "server stops accepting connections when action fails"() {
+        Action action = Mock()
+
+        given:
+        _ * action.execute(_) >> {
+            throw new RuntimeException()
+        }
+
+        when:
+        def acceptor = incomingConnector.accept(action, serializer, false)
+        async {
+            outgoingConnector.connect(acceptor.address, serializer)
+        }
+        outgoingConnector.connect(acceptor.address, serializer)
+
+        then:
+        ConnectException e = thrown()
+        e.message.startsWith "Could not connect to server ${acceptor.address}."
+        e.cause instanceof java.net.ConnectException
+
+        cleanup:
+        acceptor?.requestStop()
+    }
+
+    def "acceptor stop blocks until accept action has completed"() {
+        Action action = Mock()
+
+        given:
+        1 * action.execute(_) >> {
+            instant.connected
+            thread.block()
+            instant.actionFinished
+        }
+
+        when:
+        def acceptor = incomingConnector.accept(action, serializer, false)
+        outgoingConnector.connect(acceptor.address, serializer)
+        thread.blockUntil.connected
+        operation.stop {
+            acceptor.stop()
+        }
+
+        then:
+        operation.stop.end > instant.actionFinished
+
+        cleanup:
+        acceptor?.requestStop()
     }
 
     def "can receive message from peer after peer has closed connection"() {
         // This is a test to simulate the messaging that the daemon does on build completion, in order to validate some assumptions
 
-        def closed = new CountDownLatch(1)
-
         when:
-        def address = incomingConnector.accept({ ConnectEvent<Connection<Object>> event ->
+        def acceptor = incomingConnector.accept({ ConnectEvent<Connection<Object>> event ->
             def connection = event.connection
-            println "[server] connected"
             connection.dispatch("bye")
             connection.stop()
-            closed.countDown()
-            println "[server] disconnected"
-        } as Action, false)
-
-        def connection = outgoingConnector.connect(address)
-        println "[client] connected"
-        closed.await()
-        println "[client] receiving"
-        assert connection.receive() == "bye"
-        assert connection.receive() == null
-        connection.stop()
-        println "[client] disconnected"
-        incomingConnector.requestStop()
+            instant.closed
+        } as Action, serializer, false)
+
+        def connection = outgoingConnector.connect(acceptor.address, serializer)
+        thread.blockUntil.closed
 
         then:
-        finished()
+        connection.receive() == "bye"
+        connection.receive() == null
 
         cleanup:
-        incomingConnector.requestStop()
+        connection?.stop()
+        acceptor?.stop()
     }
 }
+
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryProcotolSerializerTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryProcotolSerializerTest.groovy
index bcdf1f8..95ef583 100644
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryProcotolSerializerTest.groovy
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryProcotolSerializerTest.groovy
@@ -58,7 +58,7 @@ class DiscoveryProcotolSerializerTest extends Specification {
     def "can read message for unknown protocol version"() {
         expect:
         def result = send { outstr ->
-            outstr.writeByte(90)
+            outstr.write(90)
         }
         result instanceof UnknownMessage
         result.toString() == "unknown protocol version 90"
@@ -67,30 +67,28 @@ class DiscoveryProcotolSerializerTest extends Specification {
     def "can read unknown message type"() {
         expect:
         def result = send { outstr ->
-            outstr.writeByte(DiscoveryProtocolSerializer.PROTOCOL_VERSION);
-            outstr.writeByte(90)
+            outstr.write(DiscoveryProtocolSerializer.PROTOCOL_VERSION);
+            outstr.write(90)
         }
         result instanceof UnknownMessage
         result.toString() == "unknown message type 90"
     }
 
     def send(Closure cl) {
-        def bytesOut = new ByteArrayOutputStream()
-        def outstr = new DataOutputStream(bytesOut)
+        def outstr = new ByteArrayOutputStream()
         cl.call(outstr)
         outstr.close()
 
-        def bytesIn = new ByteArrayInputStream(bytesOut.toByteArray())
-        return serializer.read(new DataInputStream(bytesIn), null, new SocketInetAddress(receivedAddress, 9122))
+        def bytesIn = new ByteArrayInputStream(outstr.toByteArray())
+        return serializer.newReader(bytesIn, null, new SocketInetAddress(receivedAddress, 9122)).read()
     }
 
     def send(DiscoveryMessage message) {
-        def bytesOut = new ByteArrayOutputStream()
-        def outstr = new DataOutputStream(bytesOut)
-        serializer.write(message, outstr)
+        def outstr = new ByteArrayOutputStream()
+        serializer.newWriter(outstr).write(message)
         outstr.close()
 
-        def bytesIn = new ByteArrayInputStream(bytesOut.toByteArray())
-        return serializer.read(new DataInputStream(bytesIn), null, new SocketInetAddress(receivedAddress, 9122))
+        def bytesIn = new ByteArrayInputStream(outstr.toByteArray())
+        return serializer.newReader(bytesIn, null, new SocketInetAddress(receivedAddress, 9122)).read()
     }
 }
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/DefaultSerializerTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/DefaultSerializerTest.groovy
new file mode 100644
index 0000000..5215f06
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/serialize/DefaultSerializerTest.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.messaging.serialize
+
+import spock.lang.Specification
+
+class DefaultSerializerTest extends Specification {
+    def canSerializeAndDeserializeObject() {
+        GroovyClassLoader classLoader = new GroovyClassLoader(getClass().classLoader)
+        DefaultSerializer serializer = new DefaultSerializer(classLoader)
+
+        Class cl = classLoader.parseClass('package org.gradle.cache; class TestObj implements Serializable { }')
+        Object o = cl.newInstance()
+
+        when:
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream()
+        serializer.write(outputStream, o)
+        Object r = serializer.read(new ByteArrayInputStream(outputStream.toByteArray()))
+
+        then:
+        cl.isInstance(r)
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/AbstractProcessEnvironment.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/AbstractProcessEnvironment.java
index 39e7427..8340530 100644
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/AbstractProcessEnvironment.java
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/AbstractProcessEnvironment.java
@@ -15,12 +15,14 @@
  */
 package org.gradle.internal.nativeplatform.jna;
 
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 import org.gradle.internal.nativeplatform.NativeIntegrationException;
 import org.gradle.internal.nativeplatform.ProcessEnvironment;
 import org.gradle.internal.nativeplatform.ReflectiveEnvironment;
 
 import java.io.File;
-import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 
 public abstract class AbstractProcessEnvironment implements ProcessEnvironment {
@@ -28,13 +30,13 @@ public abstract class AbstractProcessEnvironment implements ProcessEnvironment {
     private final ReflectiveEnvironment reflectiveEnvironment = new ReflectiveEnvironment();
 
     public boolean maybeSetEnvironment(Map<String, String> source) {
-        Map<String, String> currentEnv = System.getenv();
-        Iterable<String> names = new LinkedList<String>(currentEnv.keySet());
-        for (String name : names) {
-            removeEnvironmentVariable(name);
+        // need to take copy to prevent ConcurrentModificationException
+        List<String> keysToRemove = Lists.newArrayList(Sets.difference(System.getenv().keySet(), source.keySet()));
+        for (String key : keysToRemove) {
+            removeEnvironmentVariable(key);
         }
-        for (String key : source.keySet()) {
-            setEnvironmentVariable(key, source.get(key));
+        for (Map.Entry<String, String> entry : source.entrySet()) {
+            setEnvironmentVariable(entry.getKey(), entry.getValue());
         }
         return true;
     }
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibCBackedProcessEnvironment.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibCBackedProcessEnvironment.java
index ea88351..49ee1ce 100644
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibCBackedProcessEnvironment.java
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibCBackedProcessEnvironment.java
@@ -41,11 +41,7 @@ public class LibCBackedProcessEnvironment extends AbstractProcessEnvironment {
     }
 
     public void removeNativeEnvironmentVariable(String name) {
-        try {
-            libc.unsetenv(name);
-        } catch (LastErrorException lastErrorException) {
-            throw new NativeIntegrationException(String.format("Could not unset environment variable '%s'. errno: %d", name, lastErrorException.getErrorCode()));
-        }
+        setNativeEnvironmentVariable(name, "");
     }
 
     public void setNativeProcessDir(File dir) {
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/services/NativeServices.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/services/NativeServices.java
index aef509a..4e0c820 100755
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/services/NativeServices.java
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/services/NativeServices.java
@@ -20,6 +20,7 @@ import net.rubygrapefruit.platform.NativeException;
 import net.rubygrapefruit.platform.NativeIntegrationUnavailableException;
 import net.rubygrapefruit.platform.Terminals;
 import org.gradle.internal.SystemProperties;
+import org.gradle.internal.jvm.Jvm;
 import org.gradle.internal.nativeplatform.*;
 import org.gradle.internal.nativeplatform.console.ConsoleDetector;
 import org.gradle.internal.nativeplatform.console.NativePlatformConsoleDetector;
@@ -81,13 +82,19 @@ public class NativeServices extends DefaultServiceRegistry {
         return FileSystems.getDefault();
     }
 
+    protected Jvm createJvm() {
+        return Jvm.current();
+    }
+
     protected ProcessEnvironment createProcessEnvironment() {
+        ProcessEnvironment environment;
+
         OperatingSystem operatingSystem = get(OperatingSystem.class);
         try {
             if (operatingSystem.isUnix()) {
-                return new LibCBackedProcessEnvironment(get(LibC.class));
+                environment = new LibCBackedProcessEnvironment(get(LibC.class));
             } else if (operatingSystem.isWindows()) {
-                return new WindowsProcessEnvironment();
+                environment = new WindowsProcessEnvironment();
             } else {
                 return new UnsupportedEnvironment();
             }
@@ -96,6 +103,8 @@ public class NativeServices extends DefaultServiceRegistry {
             LOGGER.debug("Unable to load native library. Continuing with fallback. Failure: {}", format(e));
             return new UnsupportedEnvironment();
         }
+
+        return environment;
     }
 
     protected ConsoleDetector createConsoleDetector() {
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/CommonFileSystemTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/CommonFileSystemTest.groovy
index 70a57ed..9a07b74 100644
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/CommonFileSystemTest.groovy
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/CommonFileSystemTest.groovy
@@ -15,14 +15,14 @@
  */
 package org.gradle.internal.nativeplatform.filesystem
 
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.Requires
-import org.gradle.util.TemporaryFolder
 import org.gradle.util.TestPrecondition
 import org.junit.Rule
 import spock.lang.Specification
 
 class CommonFileSystemTest extends Specification {
-    @Rule TemporaryFolder tmpDir
+    @Rule TestNameTestDirectoryProvider tmpDir
 
     def fs = FileSystems.default
 
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnLinuxTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnLinuxTest.groovy
index 103abe6..11462df 100644
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnLinuxTest.groovy
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnLinuxTest.groovy
@@ -13,17 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.internal.nativeplatform.filesystem;
+package org.gradle.internal.nativeplatform.filesystem
 
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.Requires
-import org.gradle.util.TemporaryFolder
 import org.gradle.util.TestPrecondition
 import org.junit.Rule
 import spock.lang.Specification
 
 @Requires(TestPrecondition.LINUX)
 public class FileSystemServicesOnLinuxTest extends Specification {
-    @Rule TemporaryFolder temporaryFolder
+    @Rule TestNameTestDirectoryProvider temporaryFolder
     final Chmod chmod = FileSystemServices.services.get(Chmod)
     final Stat stat = FileSystemServices.services.get(Stat)
     final Symlink symlink = FileSystemServices.services.get(Symlink)
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnMacTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnMacTest.groovy
index 97d9234..458518e 100644
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnMacTest.groovy
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnMacTest.groovy
@@ -13,17 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.internal.nativeplatform.filesystem;
+package org.gradle.internal.nativeplatform.filesystem
 
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.Requires
-import org.gradle.util.TemporaryFolder
 import org.gradle.util.TestPrecondition
 import org.junit.Rule
 import spock.lang.Specification
 
 @Requires(TestPrecondition.MAC_OS_X)
 public class FileSystemServicesOnMacTest extends Specification {
-    @Rule TemporaryFolder temporaryFolder
+    @Rule TestNameTestDirectoryProvider temporaryFolder
     final Chmod chmod = FileSystemServices.services.get(Chmod)
     final Stat stat = FileSystemServices.services.get(Stat)
     final Symlink symlink = FileSystemServices.services.get(Symlink)
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnUnknownOsTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnUnknownOsTest.groovy
index 71763b4..8af80bd 100644
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnUnknownOsTest.groovy
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnUnknownOsTest.groovy
@@ -13,17 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.internal.nativeplatform.filesystem;
+package org.gradle.internal.nativeplatform.filesystem
 
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
-import spock.lang.Specification
 import org.junit.Rule
-import org.gradle.util.TemporaryFolder
+import spock.lang.Specification
 
 @Requires(TestPrecondition.UNKNOWN_OS)
 public class FileSystemServicesOnUnknownOsTest extends Specification {
-    @Rule TemporaryFolder temporaryFolder
+    @Rule TestNameTestDirectoryProvider temporaryFolder
     final Chmod chmod = FileSystemServices.services.get(Chmod)
     final Stat stat = FileSystemServices.services.get(Stat)
     final Symlink symlink = FileSystemServices.services.get(Symlink)
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnWindowsTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnWindowsTest.groovy
index 1c8577d..e0259e2 100644
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnWindowsTest.groovy
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnWindowsTest.groovy
@@ -13,17 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.internal.nativeplatform.filesystem;
+package org.gradle.internal.nativeplatform.filesystem
 
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.Requires
-import org.gradle.util.TemporaryFolder
 import org.gradle.util.TestPrecondition
 import org.junit.Rule
 import spock.lang.Specification
 
 @Requires(TestPrecondition.WINDOWS)
 public class FileSystemServicesOnWindowsTest extends Specification {
-    @Rule TemporaryFolder temporaryFolder
+    @Rule TestNameTestDirectoryProvider temporaryFolder
     final Chmod chmod = FileSystemServices.services.get(Chmod)
     final Stat stat = FileSystemServices.services.get(Stat)
     final Symlink symlink = FileSystemServices.services.get(Symlink)
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixJdk7FilePermissionHandlerTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixJdk7FilePermissionHandlerTest.groovy
index eccc92b..c8697fc 100644
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixJdk7FilePermissionHandlerTest.groovy
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixJdk7FilePermissionHandlerTest.groovy
@@ -16,15 +16,15 @@
 
 package org.gradle.internal.nativeplatform.filesystem.jdk7
 
-import spock.lang.Specification
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import org.junit.Rule
-import org.gradle.util.TemporaryFolder
+import spock.lang.Specification
 
 @Requires(TestPrecondition.NOT_WINDOWS)
 class PosixJdk7FilePermissionHandlerTest extends Specification {
-    @Rule TemporaryFolder temporaryFolder
+    @Rule TestNameTestDirectoryProvider temporaryFolder
 
     def "test chmod on non windows platforms with JDK7"() {
         setup:
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/jna/ProcessEnvironmentTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/jna/ProcessEnvironmentTest.groovy
index 8941b5c..4565199 100644
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/jna/ProcessEnvironmentTest.groovy
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/jna/ProcessEnvironmentTest.groovy
@@ -17,15 +17,15 @@ package org.gradle.internal.nativeplatform.jna
 
 import org.gradle.internal.nativeplatform.ProcessEnvironment
 import org.gradle.internal.nativeplatform.services.NativeServices
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.Requires
 import org.gradle.util.SetSystemProperties
-import org.gradle.util.TemporaryFolder
 import org.gradle.util.TestPrecondition
 import org.junit.Rule
 import spock.lang.Specification
 
 class ProcessEnvironmentTest extends Specification {
-    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     @Rule final SetSystemProperties systemProperties = new SetSystemProperties()
     final ProcessEnvironment env = NativeServices.getInstance().get(ProcessEnvironment)
 
@@ -59,11 +59,11 @@ class ProcessEnvironmentTest extends Specification {
         File originalDir = new File(System.getProperty("user.dir"))
 
         when:
-        env.setProcessDir(tmpDir.dir)
+        env.setProcessDir(tmpDir.testDirectory)
 
         then:
-        env.processDir.canonicalFile == tmpDir.dir
-        new File(".").canonicalFile == tmpDir.dir
+        env.processDir.canonicalFile == tmpDir.testDirectory
+        new File(".").canonicalFile == tmpDir.testDirectory
 
         cleanup:
         System.setProperty("user.dir", originalDir.absolutePath)
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy
index 7af2f2a..2bc10f7 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy
@@ -15,13 +15,13 @@
  */
 package org.gradle.integtests.openapi
 
-import org.gradle.integtests.fixtures.BasicGradleDistribution
-import org.gradle.integtests.fixtures.TestResources
 import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.executer.GradleDistribution
 import org.gradle.util.ClasspathUtil
 import org.gradle.util.DefaultClassLoaderFactory
-import org.gradle.util.TestPrecondition
 import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
 import org.junit.Assert
 import org.junit.Rule
 import org.slf4j.Logger
@@ -42,7 +42,7 @@ class CrossVersionCompatibilityIntegrationTest extends CrossVersionIntegrationSp
         checkCanBuildUsing(previous, current)
     }
 
-    void checkCanBuildUsing(BasicGradleDistribution openApiVersion, BasicGradleDistribution buildVersion) {
+    void checkCanBuildUsing(GradleDistribution openApiVersion, GradleDistribution buildVersion) {
         if (!buildVersion.openApiSupported) {
             System.out.println("skipping $buildVersion as it does not support the open API.")
             return
@@ -58,8 +58,8 @@ class CrossVersionCompatibilityIntegrationTest extends CrossVersionIntegrationSp
         def classloader = new DefaultClassLoaderFactory().createIsolatedClassLoader(classpath.collect { it.toURI() })
         def builder = classloader.loadClass(CrossVersionBuilder.class.name).newInstance()
         builder.targetGradleHomeDir = buildVersion.gradleHomeDir
-        builder.currentDir = testDir
-        builder.version = buildVersion.version
+        builder.currentDir = testDirectory
+        builder.version = buildVersion.version.version
         builder.build()
     }
 }
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy
index b2e3e8a..66cc235 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy
@@ -15,16 +15,18 @@
  */
 package org.gradle.integtests.openapi
 
-import org.gradle.integtests.fixtures.GradleDistribution
+import org.apache.commons.lang.builder.ReflectionToStringBuilder
 import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.executer.GradleDistribution
+import org.gradle.integtests.fixtures.executer.UnderDevelopmentGradleDistribution
 import org.gradle.openapi.external.runner.GradleRunnerFactory
 import org.gradle.openapi.external.runner.GradleRunnerInteractionVersion1
 import org.gradle.openapi.external.runner.GradleRunnerVersion1
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Assert
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
-import org.apache.commons.lang.builder.ReflectionToStringBuilder
 
 class GradleRunnerTest {
 
@@ -37,12 +39,13 @@ class GradleRunnerTest {
 
   private File javaprojectDir
 
-  @Rule public final GradleDistribution dist = new GradleDistribution()
+  @Rule public final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
+  final GradleDistribution dist = new UnderDevelopmentGradleDistribution()
   @Rule public final TestResources resources = new TestResources('testproject')
 
   @Before
   void setUp() {
-      javaprojectDir = dist.testDir
+      javaprojectDir = temporaryFolder.testDirectory
   }
 
   /**
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiFixture.java b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiFixture.java
index c6787b1..16ea49f 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiFixture.java
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiFixture.java
@@ -15,12 +15,13 @@
  */
 package org.gradle.integtests.openapi;
 
-import org.gradle.integtests.fixtures.GradleDistribution;
-import org.gradle.integtests.fixtures.RuleHelper;
+import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext;
 import org.gradle.internal.UncheckedException;
 import org.gradle.openapi.external.ui.DualPaneUIVersion1;
 import org.gradle.openapi.external.ui.SinglePaneUIVersion1;
 import org.gradle.openapi.external.ui.UIFactory;
+import org.gradle.test.fixtures.file.TestDirectoryProvider;
+import org.gradle.test.fixtures.file.TestDirectoryProviderFinder;
 import org.junit.Assert;
 import org.junit.rules.MethodRule;
 import org.junit.runners.model.FrameworkMethod;
@@ -32,14 +33,15 @@ import java.util.ArrayList;
 import java.util.List;
 
 public class OpenApiFixture implements MethodRule {
-    private GradleDistribution dist;
+    private IntegrationTestBuildContext buildContext = new IntegrationTestBuildContext();
+    private TestDirectoryProvider testDirectoryProvider;
     private final List<JFrame> frames = new ArrayList<JFrame>();
 
     public Statement apply(final Statement base, FrameworkMethod method, final Object target) {
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
-                dist = RuleHelper.getField(target, GradleDistribution.class);
+                testDirectoryProvider = new TestDirectoryProviderFinder().findFor(target);
                 try {
                     base.evaluate();
                 } finally {
@@ -59,7 +61,7 @@ public class OpenApiFixture implements MethodRule {
         TestSingleDualPaneUIInteractionVersion1 testSingleDualPaneUIInteractionVersion1 = new TestSingleDualPaneUIInteractionVersion1(new TestAlternateUIInteractionVersion1(), new TestSettingsNodeVersion1());
         SinglePaneUIVersion1 singlePane;
         try {
-            singlePane = UIFactory.createSinglePaneUI(getClass().getClassLoader(), dist.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false);
+            singlePane = UIFactory.createSinglePaneUI(getClass().getClassLoader(), buildContext.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false);
         } catch (Exception e) {
             throw UncheckedException.throwAsUncheckedException(e);
         }
@@ -67,8 +69,8 @@ public class OpenApiFixture implements MethodRule {
         //make sure we got something
         Assert.assertNotNull(singlePane);
 
-        singlePane.setCurrentDirectory(dist.getTestDir());
-        singlePane.addCommandLineArgumentAlteringListener(new ExtraTestCommandLineOptionsListener(dist.getUserHomeDir()));
+        singlePane.setCurrentDirectory(testDirectoryProvider.getTestDirectory());
+        singlePane.addCommandLineArgumentAlteringListener(new ExtraTestCommandLineOptionsListener(buildContext.getGradleUserHomeDir()));
 
         return singlePane;
     }
@@ -77,7 +79,7 @@ public class OpenApiFixture implements MethodRule {
         TestSingleDualPaneUIInteractionVersion1 testSingleDualPaneUIInteractionVersion1 = new TestSingleDualPaneUIInteractionVersion1(new TestAlternateUIInteractionVersion1(), new TestSettingsNodeVersion1());
         DualPaneUIVersion1 dualPane;
         try {
-            dualPane = UIFactory.createDualPaneUI(getClass().getClassLoader(), dist.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false);
+            dualPane = UIFactory.createDualPaneUI(getClass().getClassLoader(), buildContext.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false);
         } catch (Exception e) {
             throw UncheckedException.throwAsUncheckedException(e);
         }
@@ -85,8 +87,8 @@ public class OpenApiFixture implements MethodRule {
         //make sure we got something
         Assert.assertNotNull(dualPane);
 
-        dualPane.setCurrentDirectory(dist.getTestDir());
-        dualPane.addCommandLineArgumentAlteringListener(new ExtraTestCommandLineOptionsListener(dist.getUserHomeDir()));
+        dualPane.setCurrentDirectory(testDirectoryProvider.getTestDirectory());
+        dualPane.addCommandLineArgumentAlteringListener(new ExtraTestCommandLineOptionsListener(buildContext.getGradleUserHomeDir()));
 
         return dualPane;
     }
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiUiTest.groovy b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiUiTest.groovy
index e3a8160..7bf9f3e 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiUiTest.groovy
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiUiTest.groovy
@@ -15,15 +15,9 @@
  */
 package org.gradle.integtests.openapi
 
-import java.awt.Component
-import java.awt.event.HierarchyEvent
-import java.awt.event.HierarchyListener
-import java.util.concurrent.TimeUnit
-import javax.swing.JFrame
-import javax.swing.JLabel
-
-import org.gradle.integtests.fixtures.GradleDistribution
 import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext
+import org.gradle.internal.os.OperatingSystem
 import org.gradle.openapi.external.ExternalUtility
 import org.gradle.openapi.external.foundation.GradleInterfaceVersion2
 import org.gradle.openapi.external.foundation.ProjectVersion1
@@ -31,16 +25,21 @@ import org.gradle.openapi.external.foundation.RequestVersion1
 import org.gradle.openapi.external.foundation.TaskVersion1
 import org.gradle.openapi.external.foundation.favorites.FavoriteTaskVersion1
 import org.gradle.openapi.external.foundation.favorites.FavoritesEditorVersion1
-import org.gradle.internal.os.OperatingSystem
 import org.gradle.openapi.external.ui.*
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.PreconditionVerifier
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
-import org.gradle.util.PreconditionVerifier
-
 import org.junit.Assert
+import org.junit.ClassRule
 import org.junit.Rule
 import org.junit.Test
-import org.junit.ClassRule
+
+import javax.swing.*
+import java.awt.*
+import java.awt.event.HierarchyEvent
+import java.awt.event.HierarchyListener
+import java.util.concurrent.TimeUnit
 
 import static org.hamcrest.Matchers.*
 
@@ -50,7 +49,9 @@ import static org.hamcrest.Matchers.*
  */
 @Requires(TestPrecondition.SWING)
 class OpenApiUiTest {
-    @Rule public GradleDistribution dist = new GradleDistribution()
+
+    @Rule public final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
+    private IntegrationTestBuildContext buildContext = new IntegrationTestBuildContext();
     @Rule public TestResources resources = new TestResources('testproject')
     @Rule public OpenApiFixture openApi = new OpenApiFixture()
     @ClassRule public static PreconditionVerifier verifier = new PreconditionVerifier()
@@ -218,7 +219,7 @@ class OpenApiUiTest {
         Assert.assertEquals(4, editor.getFavoriteTasks().size())
 
         //now remove one of them
-        List removed1 = [favorite2]
+        java.util.List removed1 = [favorite2]
         editor.removeFavorites(removed1)
 
         //make sure it was removed
@@ -226,7 +227,7 @@ class OpenApiUiTest {
         Assert.assertEquals(3, editor.getFavoriteTasks().size())
 
         //now remove multiples
-        List removed2 = [favorite1, favorite4]
+        java.util.List removed2 = [favorite1, favorite4]
         editor.removeFavorites(removed2)
 
         //make sure they were both removed
@@ -273,7 +274,7 @@ class OpenApiUiTest {
         ((GradleInterfaceVersion2) singlePane.getGradleInterfaceVersion1()).addRequestObserver(testRequestObserver)
 
         //now execute both favorites
-        List<FavoriteTaskVersion1> favorites = [favorite1, favorite2]
+        java.util.List<FavoriteTaskVersion1> favorites = [favorite1, favorite2]
         RequestVersion1 request = ((GradleInterfaceVersion2) singlePane.getGradleInterfaceVersion1()).executeFavorites(favorites)
 
         Assert.assertNotNull(request)
@@ -309,7 +310,7 @@ class OpenApiUiTest {
 
         Assert.assertEquals("Execution Failed: " + testRequestObserver.output, 0, testRequestObserver.result)
 
-        List<ProjectVersion1> rootProjects = gradleInterface.getRootProjects();
+        java.util.List<ProjectVersion1> rootProjects = gradleInterface.getRootProjects();
         Assert.assertFalse(rootProjects.isEmpty());   //do we have any root projects?
 
         ProjectVersion1 rootProject = rootProjects.get(0);
@@ -576,12 +577,12 @@ class OpenApiUiTest {
         TestSingleDualPaneUIInteractionVersion1 testSingleDualPaneUIInteractionVersion1 = new TestSingleDualPaneUIInteractionVersion1(new TestAlternateUIInteractionVersion1(), settingsNode);
         SinglePaneUIVersion1 singlePane = null;
         try {
-            singlePane = UIFactory.createSinglePaneUI(getClass().getClassLoader(), dist.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false);
+            singlePane = UIFactory.createSinglePaneUI(getClass().getClassLoader(), buildContext.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false);
         } catch (Exception e) {
             throw new AssertionError("Failed to extract single pane: Caused by " + e.getMessage())
         }
 
-        File illegalDirectory = dist.testFile("non-existant").createDir();
+        File illegalDirectory = temporaryFolder.testDirectory.file("non-existant").createDir();
         if (illegalDirectory.equals(singlePane.getCurrentDirectory())) {
             throw new AssertionError("Directory already set to 'test' directory. The test is not setup correctly.");
         }
@@ -598,7 +599,7 @@ class OpenApiUiTest {
         //now instantiate it again
         testSingleDualPaneUIInteractionVersion1 = new TestSingleDualPaneUIInteractionVersion1(new TestAlternateUIInteractionVersion1(), settingsNode);
         try {
-            singlePane = UIFactory.createSinglePaneUI(getClass().getClassLoader(), dist.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false);
+            singlePane = UIFactory.createSinglePaneUI(getClass().getClassLoader(), buildContext.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false);
         } catch (Exception e) {
             throw new AssertionError("Failed to extract single pane (second time): Caused by " + e.getMessage())
         }
@@ -678,7 +679,7 @@ class OpenApiUiTest {
 
         Assert.assertFalse("Empty version number", version.trim().equals(""))       //shouldn't be empty
 
-        File gradleJar = ExternalUtility.getGradleJar(dist.gradleHomeDir)
+        File gradleJar = ExternalUtility.getGradleJar(buildContext.gradleHomeDir)
 
         Assert.assertNotNull("Missing gradle jar", gradleJar)                         //we should have a gradle jar
 
@@ -699,7 +700,7 @@ class OpenApiUiTest {
     void testGradleHomeDirectory() {
         SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
 
-        Assert.assertEquals(dist.gradleHomeDir, singlePane.getGradleHomeDirectory())
+        Assert.assertEquals(buildContext.gradleHomeDir, singlePane.getGradleHomeDirectory())
     }
 
     /**
@@ -853,7 +854,7 @@ class OpenApiUiTest {
         //gradle file (but it'll be the custom one.
         String name = OperatingSystem.current().getScriptName("bin/gradle");
 
-        File gradleExecutor = new File(dist.getGradleHomeDir(), name)
+        File gradleExecutor = new File(buildContext.getGradleHomeDir(), name)
 
         //make sure the executable exists
         Assert.assertTrue("Missing gradle executable at: " + gradleExecutor, gradleExecutor.exists())
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OutputUILordTest.groovy b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OutputUILordTest.groovy
index df857b5..43fb64f 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OutputUILordTest.groovy
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OutputUILordTest.groovy
@@ -13,24 +13,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.integtests.openapi;
+package org.gradle.integtests.openapi
 
-import java.awt.Font
-import java.util.concurrent.TimeUnit
-import javax.swing.UIManager
-
-import org.gradle.integtests.fixtures.GradleDistribution
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.openapi.external.ui.OutputUILordVersion1
 import org.gradle.openapi.external.ui.SinglePaneUIVersion1
-import org.gradle.util.TestPrecondition
-import org.gradle.util.Requires
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.PreconditionVerifier
-
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
 import org.junit.Assert
+import org.junit.ClassRule
 import org.junit.Rule
 import org.junit.Test
-import org.junit.ClassRule
+
+import javax.swing.*
+import java.awt.*
+import java.util.concurrent.TimeUnit
 
 import static org.hamcrest.Matchers.startsWith
 
@@ -41,7 +40,9 @@ import static org.hamcrest.Matchers.startsWith
  */
 @Requires(TestPrecondition.SWING)
 class OutputUILordTest {
-    @Rule public GradleDistribution dist = new GradleDistribution()
+
+    @Rule public final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
+
     @Rule public OpenApiFixture openApi = new OpenApiFixture()
     @Rule public TestResources resources = new TestResources('testProject')
     @ClassRule public static PreconditionVerifier verifier = new PreconditionVerifier()
@@ -58,7 +59,7 @@ class OutputUILordTest {
         OutputUILordVersion1 outputUILord = singlePane.getOutputLord()
 
         outputUILord.addFileExtension('.txt', ':')
-        List extensions = outputUILord.getFileExtensions()
+        java.util.List extensions = outputUILord.getFileExtensions()
         Assert.assertTrue(extensions.contains('.txt'))
     }
 
diff --git a/subprojects/open-api/src/test/groovy/org/gradle/openapi/external/ExternalUtilityTest.groovy b/subprojects/open-api/src/test/groovy/org/gradle/openapi/external/ExternalUtilityTest.groovy
index 7132da3..b47d9a9 100644
--- a/subprojects/open-api/src/test/groovy/org/gradle/openapi/external/ExternalUtilityTest.groovy
+++ b/subprojects/open-api/src/test/groovy/org/gradle/openapi/external/ExternalUtilityTest.groovy
@@ -15,38 +15,38 @@
  */
 package org.gradle.openapi.external
 
-import spock.lang.Specification
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
-import org.gradle.util.TestFile
+import spock.lang.Specification
 
 public class ExternalUtilityTest extends Specification {
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
     def getTheRightGradleCore() {
-        TestFile libDir = tmpDir.dir.file('lib')
+        TestFile libDir = tmpDir.testDirectory.file('lib')
 
         when:
         libDir.deleteDir()
         libDir.createDir().files('gradle-core-0.9.jar', 'gradle-core-worker-0.9.jar', 'gradle-open-api-0.9.jar')*.touch()
 
         then:
-        ExternalUtility.getGradleJar(tmpDir.dir).absolutePath == new File(libDir, 'gradle-core-0.9.jar').absolutePath
+        ExternalUtility.getGradleJar(tmpDir.testDirectory).absolutePath == new File(libDir, 'gradle-core-0.9.jar').absolutePath
 
         when:
         libDir.deleteDir()
         libDir.createDir().files('gradle-core-0.9-20100315080959+0100.jar', 'gradle-core-worker-0.9-20100315080959+0100.jar', 'gradle-open-api-0.9-20100315080959+0100.jar')*.touch()
 
         then:
-        ExternalUtility.getGradleJar(tmpDir.dir).absolutePath == new File(libDir, 'gradle-core-0.9-20100315080959+0100.jar').absolutePath
+        ExternalUtility.getGradleJar(tmpDir.testDirectory).absolutePath == new File(libDir, 'gradle-core-0.9-20100315080959+0100.jar').absolutePath
     }
 
     def failWithMultipleGradleCore() {
-        tmpDir.dir.file('lib').createDir().files('gradle-core-0.9.jar', 'gradle-core-0.10.jar', 'gradle-open-api-0.9.jar')*.touch()
+        tmpDir.testDirectory.file('lib').createDir().files('gradle-core-0.9.jar', 'gradle-core-0.10.jar', 'gradle-open-api-0.9.jar')*.touch()
 
         when:
-        ExternalUtility.getGradleJar(tmpDir.dir)
+        ExternalUtility.getGradleJar(tmpDir.testDirectory)
 
         then:
         RuntimeException e = thrown()
@@ -56,15 +56,15 @@ public class ExternalUtilityTest extends Specification {
     }
 
     def returnNullWitNonExistingGradleCore() {
-        tmpDir.dir.file('lib').createDir().files('gradle-open-api-0.9.jar')*.touch()
+        tmpDir.testDirectory.file('lib').createDir().files('gradle-open-api-0.9.jar')*.touch()
 
         expect:
-        ExternalUtility.getGradleJar(tmpDir.dir) == null
+        ExternalUtility.getGradleJar(tmpDir.testDirectory) == null
     }
 
     def failWitNonExistingGradleHome() {
         expect:
-        ExternalUtility.getGradleJar(tmpDir.dir) == null
+        ExternalUtility.getGradleJar(tmpDir.testDirectory) == null
     }
 
 }
diff --git a/subprojects/performance/performance.gradle b/subprojects/performance/performance.gradle
index d48a856..62ed02b 100644
--- a/subprojects/performance/performance.gradle
+++ b/subprojects/performance/performance.gradle
@@ -2,12 +2,14 @@ apply from: 'src/generator.groovy'
 
 configurations {
     junit
+    plugin
 }
 
 dependencies {
-    junit 'junit:junit:4.10'
+    junit 'junit:junit:4.11'
     groovy libraries.groovy
     testFixturesCompile project(':internalIntegTesting')
+    plugin gradleApi()
 }
 
 useTestFixtures()
@@ -18,7 +20,7 @@ task small(type: ProjectGeneratorTask, description: 'Generates a small project')
 task largeSrc(type: ProjectGeneratorTask, description: 'Generates a single project with lots of source files') {
     sourceFiles = 50000
     linesOfCodePerSourceFile = 20
-    withPlainAntCompile = true
+//    subProjectTemplates << 'plain-ant-compile'
 }
 
 task multi(type: ProjectGeneratorTask, description: 'Generates a multi-project build') {
@@ -26,30 +28,34 @@ task multi(type: ProjectGeneratorTask, description: 'Generates a multi-project b
     sourceFiles = 100
 }
 
-task parallel(type: ProjectGeneratorTask, description: 'Generates a multi-project build') {
-    projects = 8
-    sourceFiles = 1000
-}
-
 task mixedSize(type: ProjectGeneratorTask) {
     projects = 400
     sourceFiles = 100
     projects[1].sourceFiles = 20000
 }
 
+task withJUnit(type: ProjectGeneratorTask) {
+    projects = 1
+    sourceFiles = 100
+    subProjectTemplates << 'with-junit'
+}
+
 task withTestNG(type: ProjectGeneratorTask) {
-    projects = 2
+    projects = 1
+    sourceFiles = 100
+    subProjectTemplates << 'with-testng'
+}
+
+task withVerboseTestNG(type: ProjectGeneratorTask) {
+    projects = 1
     sourceFiles = 10
-    testReport = false
-    testFramework = 'useTestNG()'
-    testClassTemplate = 'TestNGTest.java'
+    subProjectTemplates << 'with-verbose-testng'
 }
 
-task withVerboseJUnits(type: ProjectGeneratorTask) {
-    projects = 2
+task withVerboseJUnit(type: ProjectGeneratorTask) {
+    projects = 1
     sourceFiles = 10
-    testReport = true
-    testClassTemplate = 'VerboseJUnitTest.java'
+    subProjectTemplates << 'with-verbose-junit'
 }
 
 task multiGroovy(type: ProjectGeneratorTask, description: 'Generates a multi-project groovy build') {
@@ -67,7 +73,7 @@ task largeMulti(type: ProjectGeneratorTask, description: 'Generates a large mult
     sourceFiles = 100
 }
 
-task lotDependencies(type: ProjectGeneratorTask, description: 'Generates a small multi-project build with a large Dependency Graph'){
+task lotDependencies(type: ProjectGeneratorTask, description: 'Generates a small multi-project build with a large Dependency Graph') {
     projects = 5
     sourceFiles = 100
 
@@ -78,6 +84,52 @@ task lotDependencies(type: ProjectGeneratorTask, description: 'Generates a small
     }
 }
 
+task manyProjects(type: ProjectGeneratorTask) {
+    projects = 500
+    sourceFiles = 0
+}
+
+task singleProjectNoBuildScript(type: ProjectGeneratorTask) {
+    projects = 1
+    sourceFiles = 0
+    rootProjectTemplates = []
+    subProjectTemplates = []
+}
+
+task manyProjectNoBuildScript(type: ProjectGeneratorTask) {
+    projects = 500
+    sourceFiles = 0
+    rootProjectTemplates = []
+    subProjectTemplates = []
+}
+
+task manyProjectMinimalBuildScript(type: ProjectGeneratorTask) {
+    projects = 500
+    sourceFiles = 0
+    rootProjectTemplates = ['minimal']
+    subProjectTemplates = ['minimal']
+}
+
+task manyProjectConfigInject(type: ProjectGeneratorTask) {
+    projects = 500
+    sourceFiles = 0
+    rootProjectTemplates = ['config-inject']
+    subProjectTemplates = []
+}
+
+task compilePlugin(type: JavaCompile) {
+    source 'src/configPlugin'
+    destinationDir file("$buildDir/configPlugin")
+    classpath = configurations.plugin
+}
+
+task manyProjectJavaConfig(type: ProjectGeneratorTask, dependsOn: compilePlugin) {
+    projects = 500
+    sourceFiles = 0
+    rootProjectTemplates = ['java-config']
+    subProjectTemplates = []
+}
+
 def generators = tasks.withType(ProjectGeneratorTask)
 generators.all {
     group = 'Project setup'
@@ -85,10 +137,10 @@ generators.all {
 }
 task all(dependsOn: generators)
 
-task prepareSamples(dependsOn: [small, multi, lotDependencies, withTestNG, withVerboseJUnits])
+task prepareSamples(dependsOn: [small, multi, lotDependencies, withJUnit, withTestNG, withVerboseTestNG, withVerboseJUnit])
 
 integTestTasks.all {
-    if (buildTypes.isActive('performanceTest')) {
+    if (buildTypes.isActive('performanceTest') || buildTypes.isActive('localPerformanceTest')) {
         dependsOn prepareSamples
     } else {
         enabled = false
diff --git a/subprojects/performance/src/configPlugin/ConfigPlugin.java b/subprojects/performance/src/configPlugin/ConfigPlugin.java
new file mode 100644
index 0000000..c04c745
--- /dev/null
+++ b/subprojects/performance/src/configPlugin/ConfigPlugin.java
@@ -0,0 +1,31 @@
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.tasks.testing.Test;
+import org.gradle.api.tasks.diagnostics.DependencyReportTask;
+import java.io.File;
+
+public class ConfigPlugin implements Plugin<Project> {
+    public void apply(Project project) {
+        for (Project child : project.getSubprojects()) {
+            child.getPlugins().apply("java");
+            child.getPlugins().apply("eclipse");
+            child.getPlugins().apply("idea");
+            child.getPlugins().apply(org.gradle.api.plugins.JavaPlugin.class);
+            child.getPlugins().apply(org.gradle.plugins.ide.eclipse.EclipsePlugin.class);
+            child.getPlugins().apply(org.gradle.plugins.ide.idea.IdeaPlugin.class);
+            child.getRepositories().mavenCentral();
+            child.getDependencies().add("compile", "commons-lang:commons-lang:2.5");
+            child.getDependencies().add("compile", "commons-httpclient:commons-httpclient:3.0");
+            child.getDependencies().add("compile", "commons-codec:commons-codec:1.2");
+            child.getDependencies().add("compile", "org.slf4j:jcl-over-slf4j:1.6.6");
+            child.getDependencies().add("compile", "org.codehaus:groovy:groovy-all:2.0.5");
+            child.getDependencies().add("compile", "commons-codec:commons-codec:1.2");
+            child.getDependencies().add("testCompile", "junit:junit:4.11");
+            child.getDependencies().add("testCompile", "org.testng:testng:6.4");
+            child.getDependencies().add("runtime", "com.googlecode:reflectasm:1.01");
+            Test test = (Test)(child.getTasks().getByName("test"));
+            test.jvmArgs("-XX:MaxPermSize=512m", "-XX:+HeapDumpOnOutOfMemoryError");
+            child.getTasks().add("dependencyReport", DependencyReportTask.class).setOutputFile(new File(child.getBuildDir(), "dependencies.txt"));
+        }
+    }
+}
diff --git a/subprojects/performance/src/generator.groovy b/subprojects/performance/src/generator.groovy
index 5b9885d..a1cfa21 100644
--- a/subprojects/performance/src/generator.groovy
+++ b/subprojects/performance/src/generator.groovy
@@ -1,10 +1,12 @@
 import groovy.text.SimpleTemplateEngine
 import groovy.text.Template
+import org.gradle.api.GradleException
 import org.gradle.api.tasks.TaskAction
+
 import java.text.SimpleDateFormat
+import java.util.jar.JarEntry
 import java.util.jar.JarOutputStream
 import java.util.jar.Manifest
-import java.util.jar.JarEntry
 
 class TestProject {
     final String name
@@ -38,16 +40,14 @@ class ProjectGeneratorTask extends DefaultTask {
     File destDir
     boolean groovyProject
     boolean scalaProject
-    boolean withPlainAntCompile
     int sourceFiles = 1
     Integer testSourceFiles
     int linesOfCodePerSourceFile = 5
     @InputFiles FileCollection testDependencies
-    String testClassTemplate = 'Test.java'
-    boolean testReport = false
-    String testFramework = 'useJUnit()'
 
     final List<TestProject> projects = []
+    List<String> rootProjectTemplates = ['root-project']
+    List<String> subProjectTemplates = ['project-with-source']
     final SimpleTemplateEngine engine = new SimpleTemplateEngine()
     final Map<File, Template> templates = [:]
 
@@ -118,8 +118,13 @@ class ProjectGeneratorTask extends DefaultTask {
     }
 
     def generateRootProject() {
+        def templates = subprojectNames.empty ? subProjectTemplates : rootProjectTemplates
+        if (!templates.empty) {
+            templates << 'heap-capture'
+        }
         generateProject rootProject, subprojects: subprojectNames, projectDir: destDir,
                 files: subprojectNames.empty ? [] : ['settings.gradle'],
+                templates: templates,
                 includeSource: subprojectNames.empty
 
         project.copy {
@@ -130,29 +135,43 @@ class ProjectGeneratorTask extends DefaultTask {
 
     def generateSubProject(TestProject testProject) {
         generateProject testProject, subprojects: [], projectDir: new File(destDir, testProject.name), files: [],
-                includeSource: true
+                templates: subProjectTemplates, includeSource: true
     }
 
     def generateProject(Map args, TestProject testProject) {
         File projectDir = args.projectDir
+        List<String> templates = args.templates
         logger.lifecycle "Generating test project '$testProject.name' into $projectDir"
 
-        List files = args.files + [
-                'build.gradle',
-                'pom.xml',
-                'build.xml',
-        ]
+        def files = []
+        files.addAll(args.files)
+        files.addAll(['build.gradle', 'pom.xml', 'build.xml'])
 
-        Closure generate = {String name, String templateName, Map templateArgs ->
+        Closure generate = { String name, String templateName, Map templateArgs ->
             File destFile = new File(projectDir, name)
-            File srcTemplate = project.file("src/templates/$templateName")
+            File baseFile = project.file("src/templates/$templateName")
+
+            List<File> templateFiles = []
+            if (baseFile.exists()) {
+                templateFiles << baseFile
+            }
+            templateFiles.addAll templates.collect { project.file("src/templates/$it/$templateName") }.findAll { it.exists() }
+            if (templateFiles.empty) {
+                return
+            }
+            templateFiles.subList(0, templateFiles.size() - 1).each {
+                def writer = new StringWriter()
+                getTemplate(it).make(templateArgs).writeTo(writer)
+                templateArgs.original = writer.toString()
+            }
+
             destFile.parentFile.mkdirs()
-            destFile.withWriter {Writer writer ->
-                getTemplate(srcTemplate).make(templateArgs).writeTo(writer)
+            destFile.withWriter { Writer writer ->
+                getTemplate(templateFiles.last()).make(templateArgs).writeTo(writer)
             }
         }
 
-        args += [projectName: testProject.name, groovyProject: groovyProject, scalaProject: scalaProject, withPlainAntCompile: withPlainAntCompile,
+        args += [projectName: testProject.name, groovyProject: groovyProject, scalaProject: scalaProject,
                 propertyCount: (testProject.linesOfCodePerSourceFile.intdiv(7)), repository: testProject.repository, dependencies:testProject.dependencies,
                 testProject: testProject
                 ]
@@ -170,7 +189,7 @@ class ProjectGeneratorTask extends DefaultTask {
             testProject.testSourceFiles.times {
                 String packageName = "org.gradle.test.performance${(int) (it / 100) + 1}"
                 Map classArgs = args + [packageName: packageName, productionClassName: "Production${it + 1}", testClassName: "Test${it + 1}"]
-                generate("src/test/java/${packageName.replace('.', '/')}/${classArgs.testClassName}.java", testProject.defaults.testClassTemplate, classArgs)
+                generate("src/test/java/${packageName.replace('.', '/')}/${classArgs.testClassName}.java", 'Test.java', classArgs)
             }
             if (groovyProject) {
                 testProject.sourceFiles.times {
@@ -202,7 +221,11 @@ class ProjectGeneratorTask extends DefaultTask {
     def getTemplate(File srcTemplate) {
         def template = templates[srcTemplate]
         if (!template) {
-            template = engine.createTemplate(srcTemplate)
+            try {
+                template = engine.createTemplate(srcTemplate)
+            } catch (Exception e) {
+                throw new GradleException("Could not create template from source file '$srcTemplate'", e)
+            }
             templates[srcTemplate] = template
         }
         return template
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/peformance/DependencyResolutionStressTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/peformance/DependencyResolutionStressTest.groovy
deleted file mode 100644
index 9fee778..0000000
--- a/subprojects/performance/src/integTest/groovy/org/gradle/peformance/DependencyResolutionStressTest.groovy
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.peformance
-
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.tests.fixtures.ConcurrentTestUtil
-import org.junit.Rule
-import org.junit.rules.ExternalResource
-import org.mortbay.jetty.HttpHeaders
-import org.mortbay.jetty.Server
-import org.mortbay.jetty.bio.SocketConnector
-import org.mortbay.jetty.handler.AbstractHandler
-import spock.lang.Specification
-
-import javax.servlet.http.HttpServletRequest
-import javax.servlet.http.HttpServletResponse
-import java.util.zip.ZipEntry
-import java.util.zip.ZipOutputStream
-
-class DependencyResolutionStressTest extends Specification {
-    @Rule GradleDistribution distribution = new GradleDistribution()
-    @Rule StressHttpServer server = new StressHttpServer()
-    @Rule ConcurrentTestUtil concurrent = new ConcurrentTestUtil()
-
-    def setup() {
-        distribution.requireOwnUserHomeDir()
-        concurrent.shortTimeout = 180000
-    }
-
-    def "handles concurrent access to changing artifacts"() {
-        expect:
-        4.times { count ->
-            concurrent.start {
-                def buildDir = distribution.file(count)
-                buildDir.file('build.gradle') << """
-import java.util.zip.*
-
-repositories {
-    ivy { url '${server.uri}' }
-}
-
-configurations {
-    compile
-}
-
-dependencies {
-    compile 'org.gradle:changing:1.0'
-}
-
-task check << {
-    def file = configurations.compile.singleFile
-    println "THREAD $count -> checking \$file.name size: \${file.length()}"
-    file.withInputStream { instr ->
-        def zipStream = new ZipInputStream(instr)
-        def entries = []
-        for (ZipEntry entry = zipStream.nextEntry; entry != null; entry = zipStream.nextEntry) {
-            entries << entry.name
-        }
-        assert entries == ['a', 'b']
-    }
-}
-        """
-
-                GradleDistributionExecuter executer = distribution.executer()
-                executer.withForkingExecuter()
-                10.times {
-                    executer.inDirectory(buildDir).withArgument("--refresh-dependencies").withTasks('check').run()
-                }
-            }
-        }
-        concurrent.finished()
-    }
-
-    static class StressHttpServer extends ExternalResource {
-        final Server server = new Server(0)
-        final Resources resources = new Resources()
-
-        @Override
-        protected void before() {
-            server.addConnector(new SocketConnector())
-            server.addHandler(new AbstractHandler() {
-                void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
-                    println "* Handling $request.method $request.pathInfo"
-                    if (request.method == 'GET' && request.pathInfo == '/org.gradle/changing/1.0/ivy-1.0.xml') {
-                        handleGetIvy(request, response)
-                        request.handled = true
-                    } else if (request.method == 'HEAD' && request.pathInfo == '/org.gradle/changing/1.0/ivy-1.0.xml') {
-                        handleHeadIvy(request, response)
-                        request.handled = true
-                    } else if (request.method == 'GET' && request.pathInfo == '/org.gradle/changing/1.0/changing-1.0.jar') {
-                        handleGetJar(request, response)
-                        request.handled = true
-                    } else if (request.method == 'HEAD' && request.pathInfo == '/org.gradle/changing/1.0/changing-1.0.jar') {
-                        handleHeadJar(request, response)
-                        request.handled = true
-                    }
-                }
-            })
-            server.start()
-        }
-
-        private void handleGetIvy(HttpServletRequest request, HttpServletResponse response) {
-            println "* GET IVY FILE"
-            def ivy = resources.ivy
-            response.setDateHeader(HttpHeaders.LAST_MODIFIED, ivy.lastModified)
-            response.setContentLength(ivy.contentLength)
-            response.setContentType(ivy.contentType)
-            ivy.writeContentTo(response.outputStream)
-        }
-
-        private void handleHeadIvy(HttpServletRequest request, HttpServletResponse response) {
-            println "* HEAD IVY FILE"
-            def ivy = resources.ivy
-            response.setDateHeader(HttpHeaders.LAST_MODIFIED, ivy.lastModified)
-            response.setContentLength(ivy.contentLength)
-            response.setContentType(ivy.contentType)
-        }
-
-        private void handleGetJar(HttpServletRequest request, HttpServletResponse response) {
-            println "* GET JAR"
-            def jar = resources.jar
-            response.setDateHeader(HttpHeaders.LAST_MODIFIED, jar.lastModified)
-            response.setContentLength(jar.contentLength)
-            response.setContentType(jar.contentType)
-            jar.writeContentTo(response.outputStream)
-        }
-
-        private void handleHeadJar(HttpServletRequest request, HttpServletResponse response) {
-            println "* HEAD JAR"
-            def jar = resources.jar
-            response.setDateHeader(HttpHeaders.LAST_MODIFIED, jar.lastModified)
-            response.setContentLength(jar.contentLength)
-            response.setContentType(jar.contentType)
-        }
-
-        @Override
-        protected void after() {
-            server.stop()
-        }
-
-        URI getUri() {
-            return new URI("http://localhost:${server.connectors[0].localPort}/")
-        }
-    }
-
-    static class Resources {
-        private final Object lock = new Object()
-        private int count
-        private Resource ivy
-        private Resource jar
-        private final IvyFileGenerator ivyGenerator = new IvyFileGenerator()
-        private final JarFileGenerator jarGenerator = new JarFileGenerator()
-
-        Resource getIvy() {
-            synchronized (lock) {
-                maybeReset()
-                if (ivy == null) {
-                    ivy = ivyGenerator.regenerate()
-                }
-                return ivy
-            }
-        }
-
-        Resource getJar() {
-            synchronized (lock) {
-                maybeReset()
-                if (jar == null) {
-                    jar = jarGenerator.regenerate()
-                }
-                return jar
-            }
-        }
-
-        private void maybeReset() {
-            count++
-            if (count > 4) {
-                println "*** RESET"
-                count = 0
-                ivy = null
-                jar = null
-            }
-        }
-    }
-
-    static class Resource {
-        final long lastModified
-        final byte[] content
-        final String contentType
-
-        Resource(byte[] content, long lastModified, String contentType) {
-            this.content = content
-            this.lastModified = lastModified
-            this.contentType = contentType
-        }
-
-        int getContentLength() {
-            content.length
-        }
-
-        void writeContentTo(OutputStream outputStream) {
-            outputStream.write(content)
-        }
-    }
-
-    static abstract class ResourceGenerator {
-        private final Random random = new Random();
-
-        Resource regenerate() {
-            def str = new ByteArrayOutputStream()
-            generateContent(str)
-            def content = str.toByteArray()
-            def lastModified = System.currentTimeMillis()
-            return new Resource(content, lastModified, contentType)
-        }
-
-        // Writes a long string of text encoded with the system encoding to the given output stream
-        protected void writeLongString(OutputStream outputStream) {
-            for (int i = 0; i < 25000; i++) {
-                outputStream.write(String.valueOf(random.nextInt()).bytes)
-            }
-        }
-
-        protected abstract String getContentType()
-
-        protected abstract void generateContent(OutputStream outputStream)
-    }
-
-    static class IvyFileGenerator extends ResourceGenerator {
-        @Override
-        String getContentType() {
-            return "text/xml"
-        }
-
-        @Override
-        void generateContent(OutputStream outputStream) {
-            outputStream << '''
-<ivy-module version="1.0">
-    <info organisation="org.gradle" module="changing" revision="1.0"/>
-    <!--
-'''
-            writeLongString(outputStream)
-            outputStream << '''
--->
-</ivy-module>
-'''
-        }
-    }
-
-    static class JarFileGenerator extends ResourceGenerator {
-        @Override
-        String getContentType() {
-            return "application/java-archive"
-        }
-
-        @Override
-        void generateContent(OutputStream outputStream) {
-            def zipStream = new ZipOutputStream(outputStream)
-            zipStream.putNextEntry(new ZipEntry("a"))
-            writeLongString(zipStream)
-            zipStream.putNextEntry(new ZipEntry("b"))
-            writeLongString(zipStream)
-            zipStream.finish()
-        }
-    }
-}
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/peformance/PerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/peformance/PerformanceTest.groovy
deleted file mode 100644
index 429a0b2..0000000
--- a/subprojects/performance/src/integTest/groovy/org/gradle/peformance/PerformanceTest.groovy
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.peformance
-
-import org.gradle.peformance.fixture.PerformanceTestRunner
-import spock.lang.Specification
-import spock.lang.Unroll
-
-import static org.gradle.peformance.fixture.DataAmount.*
-import static org.gradle.peformance.fixture.Duration.*
-
-/**
- * by Szczepan Faber, created at: 2/9/12
- */
-class PerformanceTest extends Specification {
-    @Unroll("Project '#testProject' clean build")
-    def "clean build"() {
-        expect:
-        def result = new PerformanceTestRunner(testProject: testProject,
-                tasksToRun: ['clean', 'build'],
-                otherVersions: ['1.0'],
-                runs: runs,
-                warmUpRuns: 1,
-                maxExecutionTimeRegression: maxExecutionTimeRegression,
-                maxMemoryRegression: kbytes(1400)
-        ).run()
-        result.assertCurrentVersionHasNotRegressed()
-
-        where:
-        testProject       | runs | maxExecutionTimeRegression
-        "small"           | 5    | millis(500)
-        "multi"           | 5    | millis(1000)
-        "lotDependencies" | 5    | millis(1000)
-    }
-
-    @Unroll("Project '#testProject' up-to-date build")
-    def "build"() {
-        expect:
-        def result = new PerformanceTestRunner(testProject: testProject,
-                tasksToRun: ['build'],
-                runs: runs,
-                warmUpRuns: 1,
-                maxExecutionTimeRegression: maxExecutionTimeRegression,
-                maxMemoryRegression: kbytes(1400)
-        ).run()
-        result.assertCurrentVersionHasNotRegressed()
-
-        where:
-        testProject       | runs | maxExecutionTimeRegression
-        "small"           | 5    | millis(500)
-        "multi"           | 5    | millis(1000)
-        "lotDependencies" | 5    | millis(1000)
-    }
-
-    @Unroll("Project '#testProject' dependency report")
-    def "dependency report"() {
-        expect:
-        def result = new PerformanceTestRunner(testProject: testProject,
-                tasksToRun: ['dependencyReport'],
-                runs: runs,
-                warmUpRuns: 1,
-                maxExecutionTimeRegression: maxExecutionTimeRegression,
-                maxMemoryRegression: kbytes(1400)
-        ).run()
-        result.assertCurrentVersionHasNotRegressed()
-
-        where:
-        testProject       | runs | maxExecutionTimeRegression
-        "lotDependencies" | 5    | millis(1000)
-    }
-
-    @Unroll("Project '#testProject' eclipse")
-    def "eclipse"() {
-        expect:
-        def result = new PerformanceTestRunner(testProject: testProject,
-                tasksToRun: ['eclipse'],
-                runs: runs,
-                warmUpRuns: 1,
-                maxExecutionTimeRegression: maxExecutionTimeRegression,
-                maxMemoryRegression: kbytes(1400)
-        ).run()
-        result.assertCurrentVersionHasNotRegressed()
-
-        where:
-        testProject       | runs | maxExecutionTimeRegression
-        "small"           | 5    | millis(500)
-        "multi"           | 5    | millis(1000)
-        "lotDependencies" | 5    | millis(1000)
-    }
-
-    @Unroll("Project '#testProject' idea")
-    def "idea"() {
-        expect:
-        def result = new PerformanceTestRunner(testProject: testProject,
-                tasksToRun: ['idea'],
-                runs: runs,
-                warmUpRuns: 1,
-                maxExecutionTimeRegression: maxExecutionTimeRegression,
-                maxMemoryRegression: kbytes(1400)
-        ).run()
-        result.assertCurrentVersionHasNotRegressed()
-
-        where:
-        testProject       | runs | maxExecutionTimeRegression
-        "small"           | 5    | millis(500)
-        "multi"           | 5    | millis(1000)
-        "lotDependencies" | 5    | millis(1000)
-    }
-
-    @Unroll("Project '#testProject'")
-    def "verbose tests with report"() {
-        when:
-        def result = new PerformanceTestRunner(testProject: testProject,
-                tasksToRun: ['cleanTest', 'test'],
-                args: ['-q'],
-                runs: runs,
-                warmUpRuns: 1,
-                maxExecutionTimeRegression: maxExecutionTimeRegression,
-                maxMemoryRegression: kbytes(1400)
-        ).run()
-
-        then:
-        result.assertCurrentVersionHasNotRegressed()
-
-        where:
-        testProject         | runs | maxExecutionTimeRegression
-        "withTestNG"        | 4    | millis(800)
-        "withVerboseJUnits" | 4    | millis(1200)
-    }
-}
\ No newline at end of file
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/CleanBuildPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/CleanBuildPerformanceTest.groovy
new file mode 100644
index 0000000..b89eb00
--- /dev/null
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/CleanBuildPerformanceTest.groovy
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance
+
+import org.gradle.performance.fixture.PerformanceTestRunner
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static org.gradle.performance.fixture.DataAmount.kbytes
+import static org.gradle.performance.fixture.Duration.millis
+
+/**
+ * by Szczepan Faber, created at: 2/9/12
+ */
+class CleanBuildPerformanceTest extends Specification {
+
+    @Unroll("Project '#testProject' clean build")
+    def "clean build"() {
+        expect:
+        def result = new PerformanceTestRunner(testProject: testProject,
+                tasksToRun: ['clean', 'build'],
+                runs: 5,
+                warmUpRuns: 1,
+                targetVersions: ['1.0', 'last'],
+                maxExecutionTimeRegression: [maxExecutionTimeRegression, maxExecutionTimeRegression],
+                maxMemoryRegression: [kbytes(3000), kbytes(3000)]
+        ).run()
+        result.assertCurrentVersionHasNotRegressed()
+
+        where:
+        testProject       | maxExecutionTimeRegression
+        "small"           | millis(500)
+        "multi"           | millis(1000)
+        "lotDependencies" | millis(1000)
+    }
+}
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyReportPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyReportPerformanceTest.groovy
new file mode 100644
index 0000000..1c9346c
--- /dev/null
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyReportPerformanceTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance
+
+import org.gradle.performance.fixture.PerformanceTestRunner
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static org.gradle.performance.fixture.DataAmount.kbytes
+import static org.gradle.performance.fixture.Duration.millis
+
+/**
+ * by Szczepan Faber, created at: 2/9/12
+ */
+class DependencyReportPerformanceTest extends Specification {
+    @Unroll("Project '#testProject' dependency report")
+    def "dependency report"() {
+        expect:
+        def result = new PerformanceTestRunner(testProject: testProject,
+                tasksToRun: ['dependencyReport'],
+                runs: 5,
+                warmUpRuns: 1,
+                targetVersions: ['1.0', '1.1', '1.2', 'last'],
+                maxExecutionTimeRegression: maxExecutionTimeRegression,
+                maxMemoryRegression: maxMemoryRegression
+        ).run()
+        result.assertCurrentVersionHasNotRegressed()
+
+        where:
+        testProject       | maxExecutionTimeRegression | maxMemoryRegression
+        "lotDependencies" | [millis(7000), millis(1000), millis(1000), millis(1000)] | [kbytes(6000), kbytes(3000), kbytes(3000), kbytes(3000)]
+    }
+}
\ No newline at end of file
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyResolutionStressTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyResolutionStressTest.groovy
new file mode 100644
index 0000000..392e727
--- /dev/null
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyResolutionStressTest.groovy
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance
+
+import org.gradle.integtests.fixtures.executer.GradleDistribution
+import org.gradle.integtests.fixtures.executer.GradleExecuter
+import org.gradle.integtests.fixtures.executer.UnderDevelopmentGradleDistribution
+import org.gradle.test.fixtures.ConcurrentTestUtil
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import org.junit.rules.ExternalResource
+import org.mortbay.jetty.HttpHeaders
+import org.mortbay.jetty.Server
+import org.mortbay.jetty.bio.SocketConnector
+import org.mortbay.jetty.handler.AbstractHandler
+import spock.lang.Specification
+
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+import java.util.zip.ZipEntry
+import java.util.zip.ZipOutputStream
+
+class DependencyResolutionStressTest extends Specification {
+    @Rule TestNameTestDirectoryProvider workspace = new TestNameTestDirectoryProvider()
+    GradleDistribution distribution = new UnderDevelopmentGradleDistribution()
+    @Rule StressHttpServer server = new StressHttpServer()
+    @Rule ConcurrentTestUtil concurrent = new ConcurrentTestUtil()
+
+    def setup() {
+        concurrent.shortTimeout = 180000
+    }
+
+    def "handles concurrent access to changing artifacts"() {
+        expect:
+        4.times { count ->
+            concurrent.start {
+                def buildDir = workspace.file(count)
+                buildDir.file('build.gradle') << """
+import java.util.zip.*
+
+repositories {
+    ivy { url '${server.uri}' }
+}
+
+configurations {
+    compile
+}
+
+dependencies {
+    compile 'org.gradle:changing:1.0'
+}
+
+task check << {
+    def file = configurations.compile.singleFile
+    println "THREAD $count -> checking \$file.name size: \${file.length()}"
+    file.withInputStream { instr ->
+        def zipStream = new ZipInputStream(instr)
+        def entries = []
+        for (ZipEntry entry = zipStream.nextEntry; entry != null; entry = zipStream.nextEntry) {
+            entries << entry.name
+        }
+        assert entries == ['a', 'b']
+    }
+}
+        """
+
+                GradleExecuter executer = distribution.executer(workspace)
+                executer.requireGradleHome(true).requireOwnGradleUserHomeDir()
+                10.times {
+                    executer.inDirectory(buildDir).withArgument("--refresh-dependencies").withTasks('check').run()
+                }
+            }
+        }
+        concurrent.finished()
+    }
+
+    static class StressHttpServer extends ExternalResource {
+        final Server server = new Server(0)
+        final Resources resources = new Resources()
+
+        @Override
+        protected void before() {
+            server.addConnector(new SocketConnector())
+            server.addHandler(new AbstractHandler() {
+                void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                    println "* Handling $request.method $request.pathInfo"
+                    if (request.method == 'GET' && request.pathInfo == '/org.gradle/changing/1.0/ivy-1.0.xml') {
+                        handleGetIvy(request, response)
+                        request.handled = true
+                    } else if (request.method == 'HEAD' && request.pathInfo == '/org.gradle/changing/1.0/ivy-1.0.xml') {
+                        handleHeadIvy(request, response)
+                        request.handled = true
+                    } else if (request.method == 'GET' && request.pathInfo == '/org.gradle/changing/1.0/changing-1.0.jar') {
+                        handleGetJar(request, response)
+                        request.handled = true
+                    } else if (request.method == 'HEAD' && request.pathInfo == '/org.gradle/changing/1.0/changing-1.0.jar') {
+                        handleHeadJar(request, response)
+                        request.handled = true
+                    }
+                }
+            })
+            server.start()
+        }
+
+        private void handleGetIvy(HttpServletRequest request, HttpServletResponse response) {
+            println "* GET IVY FILE"
+            def ivy = resources.ivy
+            response.setDateHeader(HttpHeaders.LAST_MODIFIED, ivy.lastModified)
+            response.setContentLength(ivy.contentLength)
+            response.setContentType(ivy.contentType)
+            ivy.writeContentTo(response.outputStream)
+        }
+
+        private void handleHeadIvy(HttpServletRequest request, HttpServletResponse response) {
+            println "* HEAD IVY FILE"
+            def ivy = resources.ivy
+            response.setDateHeader(HttpHeaders.LAST_MODIFIED, ivy.lastModified)
+            response.setContentLength(ivy.contentLength)
+            response.setContentType(ivy.contentType)
+        }
+
+        private void handleGetJar(HttpServletRequest request, HttpServletResponse response) {
+            println "* GET JAR"
+            def jar = resources.jar
+            response.setDateHeader(HttpHeaders.LAST_MODIFIED, jar.lastModified)
+            response.setContentLength(jar.contentLength)
+            response.setContentType(jar.contentType)
+            jar.writeContentTo(response.outputStream)
+        }
+
+        private void handleHeadJar(HttpServletRequest request, HttpServletResponse response) {
+            println "* HEAD JAR"
+            def jar = resources.jar
+            response.setDateHeader(HttpHeaders.LAST_MODIFIED, jar.lastModified)
+            response.setContentLength(jar.contentLength)
+            response.setContentType(jar.contentType)
+        }
+
+        @Override
+        protected void after() {
+            server.stop()
+        }
+
+        URI getUri() {
+            return new URI("http://localhost:${server.connectors[0].localPort}/")
+        }
+    }
+
+    static class Resources {
+        private final Object lock = new Object()
+        private int count
+        private Resource ivy
+        private Resource jar
+        private final IvyFileGenerator ivyGenerator = new IvyFileGenerator()
+        private final JarFileGenerator jarGenerator = new JarFileGenerator()
+
+        Resource getIvy() {
+            synchronized (lock) {
+                maybeReset()
+                if (ivy == null) {
+                    ivy = ivyGenerator.regenerate()
+                }
+                return ivy
+            }
+        }
+
+        Resource getJar() {
+            synchronized (lock) {
+                maybeReset()
+                if (jar == null) {
+                    jar = jarGenerator.regenerate()
+                }
+                return jar
+            }
+        }
+
+        private void maybeReset() {
+            count++
+            if (count > 4) {
+                println "*** RESET"
+                count = 0
+                ivy = null
+                jar = null
+            }
+        }
+    }
+
+    static class Resource {
+        final long lastModified
+        final byte[] content
+        final String contentType
+
+        Resource(byte[] content, long lastModified, String contentType) {
+            this.content = content
+            this.lastModified = lastModified
+            this.contentType = contentType
+        }
+
+        int getContentLength() {
+            content.length
+        }
+
+        void writeContentTo(OutputStream outputStream) {
+            outputStream.write(content)
+        }
+    }
+
+    static abstract class ResourceGenerator {
+        private final Random random = new Random();
+
+        Resource regenerate() {
+            def str = new ByteArrayOutputStream()
+            generateContent(str)
+            def content = str.toByteArray()
+            def lastModified = System.currentTimeMillis()
+            return new Resource(content, lastModified, contentType)
+        }
+
+        // Writes a long string of text encoded with the system encoding to the given output stream
+        protected void writeLongString(OutputStream outputStream) {
+            for (int i = 0; i < 25000; i++) {
+                outputStream.write(String.valueOf(random.nextInt()).bytes)
+            }
+        }
+
+        protected abstract String getContentType()
+
+        protected abstract void generateContent(OutputStream outputStream)
+    }
+
+    static class IvyFileGenerator extends ResourceGenerator {
+        @Override
+        String getContentType() {
+            return "text/xml"
+        }
+
+        @Override
+        void generateContent(OutputStream outputStream) {
+            outputStream << '''
+<ivy-module version="1.0">
+    <info organisation="org.gradle" module="changing" revision="1.0"/>
+    <!--
+'''
+            writeLongString(outputStream)
+            outputStream << '''
+-->
+</ivy-module>
+'''
+        }
+    }
+
+    static class JarFileGenerator extends ResourceGenerator {
+        @Override
+        String getContentType() {
+            return "application/java-archive"
+        }
+
+        @Override
+        void generateContent(OutputStream outputStream) {
+            def zipStream = new ZipOutputStream(outputStream)
+            zipStream.putNextEntry(new ZipEntry("a"))
+            writeLongString(zipStream)
+            zipStream.putNextEntry(new ZipEntry("b"))
+            writeLongString(zipStream)
+            zipStream.finish()
+        }
+    }
+}
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/IdeIntegrationPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/IdeIntegrationPerformanceTest.groovy
new file mode 100644
index 0000000..d0a756c
--- /dev/null
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/IdeIntegrationPerformanceTest.groovy
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance
+
+import org.gradle.performance.fixture.PerformanceTestRunner
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static org.gradle.performance.fixture.DataAmount.kbytes
+import static org.gradle.performance.fixture.Duration.millis
+
+/**
+ * by Szczepan Faber, created at: 2/9/12
+ */
+class IdeIntegrationPerformanceTest extends Specification {
+    @Unroll("Project '#testProject' eclipse")
+    def "eclipse"() {
+        expect:
+        def result = new PerformanceTestRunner(testProject: testProject,
+                tasksToRun: ['eclipse'],
+                runs: 5,
+                warmUpRuns: 1,
+                targetVersions: ['1.0', '1.1', '1.2', 'last'],
+                maxExecutionTimeRegression: maxExecutionTimeRegression,
+                maxMemoryRegression: [kbytes(3000), kbytes(3000), kbytes(3000), kbytes(3000)]
+        ).run()
+        result.assertCurrentVersionHasNotRegressed()
+
+        where:
+        testProject       | maxExecutionTimeRegression
+        "small"           | [millis(500), millis(500), millis(500), millis(500)]
+        "multi"           | [millis(1500), millis(1000), millis(1000), millis(1000)]
+        "lotDependencies" | [millis(3000), millis(1000), millis(1000), millis(1000)]
+    }
+
+    @Unroll("Project '#testProject' idea")
+    def "idea"() {
+        expect:
+        def result = new PerformanceTestRunner(testProject: testProject,
+                tasksToRun: ['idea'],
+                runs: 5,
+                warmUpRuns: 1,
+                targetVersions: ['1.0', '1.1', '1.2', 'last'],
+                maxExecutionTimeRegression: maxExecutionTimeRegression,
+                maxMemoryRegression: [kbytes(3000), kbytes(3000), kbytes(3000), kbytes(3000)]
+        ).run()
+        result.assertCurrentVersionHasNotRegressed()
+
+        where:
+        testProject       | maxExecutionTimeRegression
+        "small"           | [millis(500), millis(500), millis(500), millis(500)]
+        "multi"           | [millis(1500), millis(1000), millis(1000), millis(1000)]
+        "lotDependencies" | [millis(3000), millis(1000), millis(1000), millis(1000)]
+    }
+}
\ No newline at end of file
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/TestExecutionPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/TestExecutionPerformanceTest.groovy
new file mode 100644
index 0000000..afb55c9
--- /dev/null
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/TestExecutionPerformanceTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance
+
+import org.gradle.performance.fixture.PerformanceTestRunner
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static org.gradle.performance.fixture.DataAmount.kbytes
+import static org.gradle.performance.fixture.Duration.millis
+
+/**
+ * by Szczepan Faber, created at: 2/9/12
+ */
+class TestExecutionPerformanceTest extends Specification {
+    @Unroll("Project '#testProject'")
+    def "test execution"() {
+        when:
+        def result = new PerformanceTestRunner(testProject: testProject,
+                tasksToRun: ['cleanTest', 'test'],
+                runs: 4,
+                args: ['-q'],
+                warmUpRuns: 1,
+                targetVersions: ['1.0', '1.2', 'last'],
+                maxExecutionTimeRegression: maxExecutionTimeRegression,
+                maxMemoryRegression: [kbytes(500), kbytes(3000), kbytes(3000)]
+        ).run()
+
+        then:
+        result.assertCurrentVersionHasNotRegressed()
+
+        where:
+        testProject         | maxExecutionTimeRegression
+        "withTestNG"        | [millis(1000), millis(1000), millis(500)]
+        "withJUnit"         | [millis(500), millis(500), millis(500)]
+        "withVerboseTestNG" | [millis(500), millis(500), millis(500)]
+        "withVerboseJUnit"  | [millis(500), millis(500), millis(500)]
+    }
+}
\ No newline at end of file
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/UpToDateBuildPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/UpToDateBuildPerformanceTest.groovy
new file mode 100644
index 0000000..604e9c9
--- /dev/null
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/UpToDateBuildPerformanceTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance
+
+import org.gradle.performance.fixture.PerformanceTestRunner
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static org.gradle.performance.fixture.DataAmount.kbytes
+import static org.gradle.performance.fixture.Duration.millis
+
+/**
+ * by Szczepan Faber, created at: 2/9/12
+ */
+class UpToDateBuildPerformanceTest extends Specification {
+    @Unroll("Project '#testProject' up-to-date build")
+    def "build"() {
+        expect:
+        def result = new PerformanceTestRunner(testProject: testProject,
+                tasksToRun: ['build'],
+                runs: 5,
+                warmUpRuns: 1,
+                targetVersions: ['1.0', 'last'],
+                maxExecutionTimeRegression: [maxExecutionTimeRegression, maxExecutionTimeRegression],
+                maxMemoryRegression: [kbytes(3000), kbytes(3000)]
+        ).run()
+        result.assertCurrentVersionHasNotRegressed()
+
+        where:
+        testProject       | maxExecutionTimeRegression
+        "small"           | millis(500)
+        "multi"           | millis(1000)
+        "lotDependencies" | millis(1000)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/performance/src/templates/build.gradle b/subprojects/performance/src/templates/build.gradle
deleted file mode 100644
index e22fa91..0000000
--- a/subprojects/performance/src/templates/build.gradle
+++ /dev/null
@@ -1,148 +0,0 @@
-import java.lang.management.ManagementFactory
-import java.math.RoundingMode
-
-/*
-* Copyright 2010 the original author or authors.
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*      http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-<% if (subprojects.empty ) { %>
-apply plugin: 'java'
-apply plugin: 'eclipse'
-apply plugin: 'idea'
-
-repositories {
-<% if (repository) { %>
-    maven {
-        url "${repository.getUri()}"
-    }
-<% } %>
-    mavenCentral()
-}
-
-dependencies {
-    compile 'commons-lang:commons-lang:2.5'
-    compile "commons-httpclient:commons-httpclient:3.0"
-    compile "commons-codec:commons-codec:1.2"
-    compile "org.slf4j:jcl-over-slf4j:1.6.6"
-    compile "org.codehaus.groovy:groovy:1.8.8"
-    compile "commons-codec:commons-codec:1.2"
-    testCompile 'junit:junit:4.8.2'
-    testCompile 'org.testng:testng:6.4'
-    runtime 'com.googlecode:reflectasm:1.01'
-
-    <% if (dependencies) { dependencies.each { %>
-    compile "${it.shortNotation()}" <% } %>
-    <% } %>
-}
-
-test {
-    jvmArgs '-XX:MaxPermSize=512m', '-XX:+HeapDumpOnOutOfMemoryError'
-    testReport = <%=testProject.defaults.testReport%>
-    <%=testProject.defaults.testFramework%>
-}
-
-<% if (groovyProject) { %>
-apply plugin: 'groovy'
-dependencies {
-    groovy 'org.codehaus.groovy:groovy:1.8.8'
-}
-<% } %>
-
-<% if (scalaProject) { %>
-apply plugin: 'scala'
-dependencies {
-    scalaTools 'org.scala-lang:scala-compiler:2.9.2'
-    compile 'org.scala-lang:scala-library:2.9.2'
-}
-tasks.withType(ScalaCompile) {
-    scalaCompileOptions.with {
-        useAnt = false
-        fork = true
-        forkOptions.jvmArgs = ["-XX:MaxPermSize=512m"]
-    }
-}
-<% } %>
-
-<% if (withPlainAntCompile) { %>
-class PlainAntCompile extends DefaultTask{
-	File outputDirectoy
-	File sourceDirectory
-	FileCollection classpath;
-
-	@TaskAction
-	void compile(){
-		outputDirectoy.mkdirs()
-		project.ant {
-			property(name:"build.sysclasspath", value:"last")
-			path(id:'classpath') {
-				pathelement(path:classpath.asPath)
-			}
-			path(id:'sourcepath') {
-				pathelement(location:outputDirectoy.absolutePath)
-			}
-			presetdef(name: 'antCompileJava') {
-				javac(  classpathref:'classpath',
-						encoding:'UTF-8',
-						destdir:outputDirectoy.absolutePath,
-						excludes:'',
-						fork:'true',
-						memoryInitialSize:'1024m',
-						memoryMaximumSize:'1536m',
-						source:'1.6',
-						srcdir:sourceDirectory.absolutePath,
-						target:'1.6')
-			}
-		}
-		project.ant.antCompileJava()
-	}
-}
-
-
-task plainAntCompile(type:PlainAntCompile){
-	sourceDirectory = file("src/main/java")
-	outputDirectoy = file("\$buildDir/classes/main")
-	classpath = sourceSets.main.compileClasspath
-}
-
-<% } %>
-
-task dependencyReport(type: DependencyReportTask) {
-    outputFile = new File(buildDir, "dependencies.txt")
-}
-<% } %>
-
-if (project == rootProject) {
-    gradle.buildFinished {
-        def heap = ManagementFactory.memoryMXBean.heapMemoryUsage
-        def nonHeap = ManagementFactory.memoryMXBean.nonHeapMemoryUsage
-        logger.lifecycle "BEFORE GC"
-        logger.lifecycle "heap: \${format(heap.used)} (initial \${format(heap.init)}, commited \${format(heap.committed)}, max \${format(heap.max)}"
-        logger.lifecycle "nonHeap: \${format(nonHeap.used)} (initial \${format(nonHeap.init)}, commited \${format(nonHeap.committed)}, max \${format(nonHeap.max)}"
-
-        ManagementFactory.memoryMXBean.gc()
-        heap = ManagementFactory.memoryMXBean.heapMemoryUsage
-        nonHeap = ManagementFactory.memoryMXBean.nonHeapMemoryUsage
-        logger.lifecycle "AFTER GC"
-        logger.lifecycle "heap: \${format(heap.used)} (initial \${format(heap.init)}, commited \${format(heap.committed)}, max \${format(heap.max)}"
-        logger.lifecycle "nonHeap: \${format(nonHeap.used)} (initial \${format(nonHeap.init)}, commited \${format(nonHeap.committed)}, max \${format(nonHeap.max)}"
-        buildDir.mkdirs()
-        new File(buildDir, "totalMemoryUsed.txt").text = heap.used
-    }
-}
-
-def format(def value) {
-    value = value / (1024 * 1024)
-    value = value.setScale(4, RoundingMode.DOWN)
-    return "\${value}MB"
-}
diff --git a/subprojects/performance/src/templates/build.xml b/subprojects/performance/src/templates/build.xml
deleted file mode 100644
index f5383b1..0000000
--- a/subprojects/performance/src/templates/build.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<project>
-    <property name="lib.dir" location="lib/test"/>
-
-<% if (!subprojects.empty ) {
-    ['clean', 'jar', 'build'].each { target ->
-%>
-    <target name="$target">
-        <% subprojects.each { subproject -> %>
-            <ant dir="$subproject" target="$target">
-                <property name="lib.dir" location="\${lib.dir}"/>
-            </ant>
-        <%  } %>
-    </target>
-<% }
-} else { %>
-
-    <property name="build.dir" location="ant-build"/>
-    <property name="src.dir" location="src/main/java"/>
-    <property name="test.src.dir" location="src/test/java"/>
-    <property name="classes.dir" location="\${build.dir}/classes"/>
-    <property name="test.classes.dir" location="\${build.dir}/test-classes"/>
-    <property name="test.reports.dir" location="\${build.dir}/test-reports"/>
-
-    <target name="clean">
-        <delete dir="\${build.dir}"/>
-    </target>
-
-    <target name="compile">
-        <mkdir dir="\${classes.dir}"/>
-        <javac srcdir="\${src.dir}" destdir="\${classes.dir}"/>
-    </target>
-
-    <target name="compileTest" depends="compile">
-        <mkdir dir="\${test.classes.dir}"/>
-        <javac srcdir="\${test.src.dir}" destdir="\${test.classes.dir}">
-            <classpath>
-                <path location="\${classes.dir}"/>
-                <fileset dir="\${lib.dir}"/>
-            </classpath>
-        </javac>
-    </target>
-
-    <target name="test" depends="compile, compileTest">
-        <mkdir dir="\${test.reports.dir}"/>
-        <junit>
-            <classpath>
-                <path location="\${test.classes.dir}"/>
-                <path location="\${classes.dir}"/>
-                <fileset dir="\${lib.dir}"/>
-            </classpath>
-            <batchtest todir="\${test.reports.dir}">
-                <fileset dir="\${test.classes.dir}" includes="**/*Test*.class"/>
-            </batchtest>
-            <formatter type="xml"/>
-        </junit>
-        <!--<junitreport toDir="\${build.dir}">-->
-            <!--<fileset dir="\${test.reports.dir}" includes="*.xml"/>-->
-            <!--<report todir="\${test.reports.dir}"/>-->
-        <!--</junitreport>-->
-    </target>
-
-    <target name="jar" depends="compile">
-        <jar destfile="\${build.dir}/production.jar">
-            <fileset dir="\${classes.dir}"/>
-        </jar>
-    </target>
-
-    <target name="build" depends="jar, test"/>
-<% } %>
-</project>
diff --git a/subprojects/performance/src/templates/config-inject/build.gradle b/subprojects/performance/src/templates/config-inject/build.gradle
new file mode 100644
index 0000000..3bf7b0c
--- /dev/null
+++ b/subprojects/performance/src/templates/config-inject/build.gradle
@@ -0,0 +1,60 @@
+subprojects {
+
+apply plugin: 'java'
+apply plugin: 'eclipse'
+apply plugin: 'idea'
+
+repositories {
+<% if (repository) { %>
+    maven {
+        url "${repository.getUri()}"
+    }
+<% } %>
+    mavenCentral()
+}
+
+dependencies {
+    compile 'commons-lang:commons-lang:2.5'
+    compile "commons-httpclient:commons-httpclient:3.0"
+    compile "commons-codec:commons-codec:1.2"
+    compile "org.slf4j:jcl-over-slf4j:1.6.6"
+    compile "org.codehaus:groovy:groovy-all:2.0.5"
+    compile "commons-codec:commons-codec:1.2"
+    testCompile 'junit:junit:4.11'
+    testCompile 'org.testng:testng:6.4'
+    runtime 'com.googlecode:reflectasm:1.01'
+
+    <% if (dependencies) { dependencies.each { %>
+    compile "${it.shortNotation()}" <% } %>
+    <% } %>
+}
+
+test {
+    jvmArgs '-XX:MaxPermSize=512m', '-XX:+HeapDumpOnOutOfMemoryError'
+}
+
+<% if (groovyProject) { %>
+apply plugin: 'groovy'
+dependencies {
+    compile 'org.codehaus:groovy:groovy-all:2.0.5'
+}
+<% } %>
+
+<% if (scalaProject) { %>
+apply plugin: 'scala'
+dependencies {
+    compile 'org.scala-lang:scala-library:2.9.2'
+}
+tasks.withType(ScalaCompile) {
+    scalaCompileOptions.with {
+        useAnt = false
+        fork = true
+        forkOptions.jvmArgs = ["-XX:MaxPermSize=512m"]
+    }
+}
+<% } %>
+
+task dependencyReport(type: DependencyReportTask) {
+    outputFile = new File(buildDir, "dependencies.txt")
+}
+}
diff --git a/subprojects/performance/src/templates/heap-capture/build.gradle b/subprojects/performance/src/templates/heap-capture/build.gradle
new file mode 100644
index 0000000..787c2d5
--- /dev/null
+++ b/subprojects/performance/src/templates/heap-capture/build.gradle
@@ -0,0 +1,27 @@
+${original}
+
+import java.lang.management.ManagementFactory
+import java.math.RoundingMode
+
+gradle.buildFinished {
+    def heap = ManagementFactory.memoryMXBean.heapMemoryUsage
+    def nonHeap = ManagementFactory.memoryMXBean.nonHeapMemoryUsage
+    logger.lifecycle "BEFORE GC"
+    logger.lifecycle "heap: \${format(heap.used)} (initial \${format(heap.init)}, commited \${format(heap.committed)}, max \${format(heap.max)}"
+    logger.lifecycle "nonHeap: \${format(nonHeap.used)} (initial \${format(nonHeap.init)}, commited \${format(nonHeap.committed)}, max \${format(nonHeap.max)}"
+
+    ManagementFactory.memoryMXBean.gc()
+    heap = ManagementFactory.memoryMXBean.heapMemoryUsage
+    nonHeap = ManagementFactory.memoryMXBean.nonHeapMemoryUsage
+    logger.lifecycle "AFTER GC"
+    logger.lifecycle "heap: \${format(heap.used)} (initial \${format(heap.init)}, commited \${format(heap.committed)}, max \${format(heap.max)}"
+    logger.lifecycle "nonHeap: \${format(nonHeap.used)} (initial \${format(nonHeap.init)}, commited \${format(nonHeap.committed)}, max \${format(nonHeap.max)}"
+    buildDir.mkdirs()
+    new File(buildDir, "totalMemoryUsed.txt").text = heap.used
+}
+
+def format(def value) {
+    value = value / (1024 * 1024)
+    value = value.setScale(4, RoundingMode.DOWN)
+    return "\${value}MB"
+}
diff --git a/subprojects/performance/src/templates/java-config/build.gradle b/subprojects/performance/src/templates/java-config/build.gradle
new file mode 100644
index 0000000..86af976
--- /dev/null
+++ b/subprojects/performance/src/templates/java-config/build.gradle
@@ -0,0 +1,7 @@
+buildscript {
+    dependencies {
+        classpath files("../configPlugin")
+    }
+}
+
+apply plugin: ConfigPlugin
diff --git a/subprojects/performance/src/templates/minimal/build.gradle b/subprojects/performance/src/templates/minimal/build.gradle
new file mode 100644
index 0000000..f7a1af7
--- /dev/null
+++ b/subprojects/performance/src/templates/minimal/build.gradle
@@ -0,0 +1 @@
+project.buildDir = 'target'
diff --git a/subprojects/performance/src/templates/plain-ant-compile/build.gradle b/subprojects/performance/src/templates/plain-ant-compile/build.gradle
new file mode 100644
index 0000000..d59ba81
--- /dev/null
+++ b/subprojects/performance/src/templates/plain-ant-compile/build.gradle
@@ -0,0 +1,40 @@
+${original}
+
+class PlainAntCompile extends DefaultTask{
+	File outputDirectoy
+	File sourceDirectory
+	FileCollection classpath;
+
+	@TaskAction
+	void compile(){
+		outputDirectoy.mkdirs()
+		project.ant {
+			property(name:"build.sysclasspath", value:"last")
+			path(id:'classpath') {
+				pathelement(path:classpath.asPath)
+			}
+			path(id:'sourcepath') {
+				pathelement(location:outputDirectoy.absolutePath)
+			}
+			presetdef(name: 'antCompileJava') {
+				javac(  classpathref:'classpath',
+						encoding:'UTF-8',
+						destdir:outputDirectoy.absolutePath,
+						excludes:'',
+						fork:'true',
+						memoryInitialSize:'1024m',
+						memoryMaximumSize:'1536m',
+						source:'1.6',
+						srcdir:sourceDirectory.absolutePath,
+						target:'1.6')
+			}
+		}
+		project.ant.antCompileJava()
+	}
+}
+
+task plainAntCompile(type:PlainAntCompile){
+	sourceDirectory = file("src/main/java")
+	outputDirectoy = file("\$buildDir/classes/main")
+	classpath = sourceSets.main.compileClasspath
+}
diff --git a/subprojects/performance/src/templates/pom.xml b/subprojects/performance/src/templates/pom.xml
deleted file mode 100644
index 6ef4a78..0000000
--- a/subprojects/performance/src/templates/pom.xml
+++ /dev/null
@@ -1,102 +0,0 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-    <groupId>org.gradle</groupId>
-    <artifactId>${projectName}</artifactId>
-<% if (!subprojects.empty ) { %>
-    <packaging>pom</packaging>
-    <modules>
-        <% subprojects.each { out.println "<module>$it</module>" } %>
-    </modules>
-<% } else { %>
-    <packaging>jar</packaging>
-<% } %>
-    <version>1.0-SNAPSHOT</version>
-    <% if (repository ) { %>
-    <repositories>
-        <repository>
-          <id>local-repo</id>
-          <url>${repository.getUri()}</url>
-        </repository>
-      </repositories>
-    <% } %>
-    <dependencies>
-        <dependency>
-            <groupId>commons-lang</groupId>
-            <artifactId>commons-lang</artifactId>
-            <version>2.5</version>
-        </dependency>
-        <dependency>
-            <groupId>commons-httpclient</groupId>
-            <artifactId>commons-httpclient</artifactId>
-            <version>3.0</version>
-        </dependency>
-        <dependency>
-            <groupId>commons-codec</groupId>
-            <artifactId>commons-codec</artifactId>
-            <version>1.2</version>
-        </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>jcl-over-slf4j</artifactId>
-            <version>1.6.4</version>
-        </dependency>
-        <dependency>
-            <groupId>org.codehaus.groovy</groupId>
-            <artifactId>groovy</artifactId>
-            <version>1.8.4</version>
-        </dependency>
-        <dependency>
-            <groupId>com.googlecode</groupId>
-            <artifactId>reflectasm</artifactId>
-            <version>1.01</version>
-            <scope>runtime</scope>
-        </dependency>
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <version>4.8.2</version>
-            <scope>test</scope>
-        </dependency>
-    <% if (dependencies) { dependencies.each { dep -> %>
-        <dependency>
-            <groupId>${dep.groupId}</groupId>
-            <artifactId>${dep.artifactId}</artifactId>
-            <version>${dep.version}</version>
-        </dependency> <% } %>  <% } %>
-    </dependencies>
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <configuration>
-                    <source>1.5</source>
-                    <target>1.5</target>
-                </configuration>
-            </plugin>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-surefire-plugin</artifactId>
-                <!--<version>2.5</version>-->
-                <!--<configuration>-->
-                    <!--<parallel>classes</parallel>-->
-                    <!--<threadCount>4</threadCount>-->
-                <!--</configuration>-->
-            </plugin>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-surefire-report-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <id>test-report</id>
-                        <goals>
-                            <goal>report-only</goal>
-                        </goals>
-                        <phase>verify</phase>
-                    </execution>
-                </executions>
-            </plugin>
-        </plugins>
-    </build>
-</project>
diff --git a/subprojects/performance/src/templates/Production.groovy b/subprojects/performance/src/templates/project-with-source/Production.groovy
similarity index 100%
rename from subprojects/performance/src/templates/Production.groovy
rename to subprojects/performance/src/templates/project-with-source/Production.groovy
diff --git a/subprojects/performance/src/templates/Production.java b/subprojects/performance/src/templates/project-with-source/Production.java
similarity index 100%
rename from subprojects/performance/src/templates/Production.java
rename to subprojects/performance/src/templates/project-with-source/Production.java
diff --git a/subprojects/performance/src/templates/Production.scala b/subprojects/performance/src/templates/project-with-source/Production.scala
similarity index 100%
rename from subprojects/performance/src/templates/Production.scala
rename to subprojects/performance/src/templates/project-with-source/Production.scala
diff --git a/subprojects/performance/src/templates/Test.groovy b/subprojects/performance/src/templates/project-with-source/Test.groovy
similarity index 100%
rename from subprojects/performance/src/templates/Test.groovy
rename to subprojects/performance/src/templates/project-with-source/Test.groovy
diff --git a/subprojects/performance/src/templates/Test.java b/subprojects/performance/src/templates/project-with-source/Test.java
similarity index 100%
rename from subprojects/performance/src/templates/Test.java
rename to subprojects/performance/src/templates/project-with-source/Test.java
diff --git a/subprojects/performance/src/templates/Test.scala b/subprojects/performance/src/templates/project-with-source/Test.scala
similarity index 100%
rename from subprojects/performance/src/templates/Test.scala
rename to subprojects/performance/src/templates/project-with-source/Test.scala
diff --git a/subprojects/performance/src/templates/project-with-source/build.gradle b/subprojects/performance/src/templates/project-with-source/build.gradle
new file mode 100644
index 0000000..29561d0
--- /dev/null
+++ b/subprojects/performance/src/templates/project-with-source/build.gradle
@@ -0,0 +1,57 @@
+apply plugin: 'java'
+apply plugin: 'eclipse'
+apply plugin: 'idea'
+
+repositories {
+<% if (repository) { %>
+    maven {
+        url "${repository.getUri()}"
+    }
+<% } %>
+    mavenCentral()
+}
+
+dependencies {
+    compile 'commons-lang:commons-lang:2.5'
+    compile "commons-httpclient:commons-httpclient:3.0"
+    compile "commons-codec:commons-codec:1.2"
+    compile "org.slf4j:jcl-over-slf4j:1.6.6"
+    compile "org.codehaus.groovy:groovy:2.0.5"
+    compile "commons-codec:commons-codec:1.2"
+    testCompile 'junit:junit:4.11'
+    testCompile 'org.testng:testng:6.4'
+    runtime 'com.googlecode:reflectasm:1.01'
+
+    <% if (dependencies) { dependencies.each { %>
+    compile "${it.shortNotation()}" <% } %>
+    <% } %>
+}
+
+test {
+    jvmArgs '-XX:MaxPermSize=512m', '-XX:+HeapDumpOnOutOfMemoryError'
+}
+
+<% if (groovyProject) { %>
+apply plugin: 'groovy'
+dependencies {
+    compile 'org.codehaus:groovy:groovy-all:2.0.5'
+}
+<% } %>
+
+<% if (scalaProject) { %>
+apply plugin: 'scala'
+dependencies {
+    compile 'org.scala-lang:scala-library:2.9.2'
+}
+tasks.withType(ScalaCompile) {
+    scalaCompileOptions.with {
+        useAnt = false
+        fork = true
+        forkOptions.jvmArgs = ["-XX:MaxPermSize=512m"]
+    }
+}
+<% } %>
+
+task dependencyReport(type: DependencyReportTask) {
+    outputFile = new File(buildDir, "dependencies.txt")
+}
diff --git a/subprojects/performance/src/templates/project-with-source/build.xml b/subprojects/performance/src/templates/project-with-source/build.xml
new file mode 100644
index 0000000..282bec6
--- /dev/null
+++ b/subprojects/performance/src/templates/project-with-source/build.xml
@@ -0,0 +1,56 @@
+<project>
+    <property name="lib.dir" location="lib/test"/>
+
+    <property name="build.dir" location="ant-build"/>
+    <property name="src.dir" location="src/main/java"/>
+    <property name="test.src.dir" location="src/test/java"/>
+    <property name="classes.dir" location="\${build.dir}/classes"/>
+    <property name="test.classes.dir" location="\${build.dir}/test-classes"/>
+    <property name="test.reports.dir" location="\${build.dir}/test-reports"/>
+
+    <target name="clean">
+        <delete dir="\${build.dir}"/>
+    </target>
+
+    <target name="compile">
+        <mkdir dir="\${classes.dir}"/>
+        <javac srcdir="\${src.dir}" destdir="\${classes.dir}"/>
+    </target>
+
+    <target name="compileTest" depends="compile">
+        <mkdir dir="\${test.classes.dir}"/>
+        <javac srcdir="\${test.src.dir}" destdir="\${test.classes.dir}">
+            <classpath>
+                <path location="\${classes.dir}"/>
+                <fileset dir="\${lib.dir}"/>
+            </classpath>
+        </javac>
+    </target>
+
+    <target name="test" depends="compile, compileTest">
+        <mkdir dir="\${test.reports.dir}"/>
+        <junit>
+            <classpath>
+                <path location="\${test.classes.dir}"/>
+                <path location="\${classes.dir}"/>
+                <fileset dir="\${lib.dir}"/>
+            </classpath>
+            <batchtest todir="\${test.reports.dir}">
+                <fileset dir="\${test.classes.dir}" includes="**/*Test*.class"/>
+            </batchtest>
+            <formatter type="xml"/>
+        </junit>
+        <!--<junitreport toDir="\${build.dir}">-->
+            <!--<fileset dir="\${test.reports.dir}" includes="*.xml"/>-->
+            <!--<report todir="\${test.reports.dir}"/>-->
+        <!--</junitreport>-->
+    </target>
+
+    <target name="jar" depends="compile">
+        <jar destfile="\${build.dir}/production.jar">
+            <fileset dir="\${classes.dir}"/>
+        </jar>
+    </target>
+
+    <target name="build" depends="jar, test"/>
+</project>
diff --git a/subprojects/performance/src/templates/project-with-source/pom.xml b/subprojects/performance/src/templates/project-with-source/pom.xml
new file mode 100644
index 0000000..cb71573
--- /dev/null
+++ b/subprojects/performance/src/templates/project-with-source/pom.xml
@@ -0,0 +1,101 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.gradle</groupId>
+    <artifactId>${projectName}</artifactId>
+    <packaging>jar</packaging>
+    <version>1.0-SNAPSHOT</version>
+    <% if (repository ) { %>
+    <repositories>
+        <repository>
+          <id>local-repo</id>
+          <url>${repository.getUri()}</url>
+        </repository>
+      </repositories>
+    <% } %>
+    <dependencies>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.5</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-httpclient</groupId>
+            <artifactId>commons-httpclient</artifactId>
+            <version>3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <version>1.6.6</version>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.groovy</groupId>
+            <artifactId>groovy</artifactId>
+            <version>2.0.5</version>
+        </dependency>
+        <dependency>
+            <groupId>com.googlecode</groupId>
+            <artifactId>reflectasm</artifactId>
+            <version>1.01</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.11</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <version>6.4</version>
+            <scope>test</scope>
+        </dependency>
+    <% if (dependencies) { dependencies.each { dep -> %>
+        <dependency>
+            <groupId>${dep.groupId}</groupId>
+            <artifactId>${dep.artifactId}</artifactId>
+            <version>${dep.version}</version>
+        </dependency> <% } %>  <% } %>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.5</source>
+                    <target>1.5</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <!--<version>2.5</version>-->
+                <!--<configuration>-->
+                    <!--<parallel>classes</parallel>-->
+                    <!--<threadCount>4</threadCount>-->
+                <!--</configuration>-->
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-report-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>test-report</id>
+                        <goals>
+                            <goal>report-only</goal>
+                        </goals>
+                        <phase>verify</phase>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/subprojects/core/src/test/resources/org/gradle/api/internal/xml-transformer-test.dtd b/subprojects/performance/src/templates/root-project/build.gradle
similarity index 100%
rename from subprojects/core/src/test/resources/org/gradle/api/internal/xml-transformer-test.dtd
rename to subprojects/performance/src/templates/root-project/build.gradle
diff --git a/subprojects/performance/src/templates/root-project/build.xml b/subprojects/performance/src/templates/root-project/build.xml
new file mode 100644
index 0000000..ae346eb
--- /dev/null
+++ b/subprojects/performance/src/templates/root-project/build.xml
@@ -0,0 +1,13 @@
+<project>
+    <property name="lib.dir" location="lib/test"/>
+
+<% ['clean', 'jar', 'build'].each { target -> %>
+    <target name="$target">
+        <% subprojects.each { subproject -> %>
+            <ant dir="$subproject" target="$target">
+                <property name="lib.dir" location="\${lib.dir}"/>
+            </ant>
+        <%  } %>
+    </target>
+<% } %>
+</project>
diff --git a/subprojects/performance/src/templates/root-project/pom.xml b/subprojects/performance/src/templates/root-project/pom.xml
new file mode 100644
index 0000000..3dc02df
--- /dev/null
+++ b/subprojects/performance/src/templates/root-project/pom.xml
@@ -0,0 +1,10 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.gradle</groupId>
+    <artifactId>${projectName}</artifactId>
+    <packaging>pom</packaging>
+    <modules>
+        <% subprojects.each { out.println "<module>$it</module>" } %>
+    </modules>
+</project>
diff --git a/subprojects/performance/src/templates/with-junit/Test.java b/subprojects/performance/src/templates/with-junit/Test.java
new file mode 100644
index 0000000..2bc0961
--- /dev/null
+++ b/subprojects/performance/src/templates/with-junit/Test.java
@@ -0,0 +1,15 @@
+package ${packageName};
+
+import static org.junit.Assert.*;
+
+public class ${testClassName} {
+
+    private final ${productionClassName} production = new ${productionClassName}("value");
+
+<% 20.times { index ->  %>
+    @org.junit.Test
+    public void test${index}() {
+        assertEquals(production.getProperty(), "value");
+    }
+<% } %>
+}
diff --git a/subprojects/performance/src/templates/with-testng/Test.java b/subprojects/performance/src/templates/with-testng/Test.java
new file mode 100644
index 0000000..33da5ef
--- /dev/null
+++ b/subprojects/performance/src/templates/with-testng/Test.java
@@ -0,0 +1,15 @@
+package ${packageName};
+
+import org.testng.annotations.*;
+import static org.testng.Assert.*;
+
+public class ${testClassName} {
+    private final ${productionClassName} production = new ${productionClassName}("value");
+
+<% 20.times { index -> %>
+    @Test
+    public void test${index}() {
+        assertEquals(production.getProperty(), "value");
+    }
+<% } %>
+}
diff --git a/subprojects/performance/src/templates/with-testng/build.gradle b/subprojects/performance/src/templates/with-testng/build.gradle
new file mode 100644
index 0000000..05a9cd8
--- /dev/null
+++ b/subprojects/performance/src/templates/with-testng/build.gradle
@@ -0,0 +1,7 @@
+${original}
+
+test {
+    useTestNG()
+    maxParallelForks = 2
+    testReport = true
+}
diff --git a/subprojects/performance/src/templates/VerboseJUnitTest.java b/subprojects/performance/src/templates/with-verbose-junit/Test.java
similarity index 100%
rename from subprojects/performance/src/templates/VerboseJUnitTest.java
rename to subprojects/performance/src/templates/with-verbose-junit/Test.java
diff --git a/subprojects/performance/src/templates/TestNGTest.java b/subprojects/performance/src/templates/with-verbose-testng/Test.java
similarity index 100%
rename from subprojects/performance/src/templates/TestNGTest.java
rename to subprojects/performance/src/templates/with-verbose-testng/Test.java
diff --git a/subprojects/performance/src/templates/with-verbose-testng/build.gradle b/subprojects/performance/src/templates/with-verbose-testng/build.gradle
new file mode 100644
index 0000000..05a9cd8
--- /dev/null
+++ b/subprojects/performance/src/templates/with-verbose-testng/build.gradle
@@ -0,0 +1,7 @@
+${original}
+
+test {
+    useTestNG()
+    maxParallelForks = 2
+    testReport = true
+}
diff --git a/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/AmountTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/AmountTest.groovy
deleted file mode 100644
index 0ea8747..0000000
--- a/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/AmountTest.groovy
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.peformance.fixture
-
-import spock.lang.Specification
-
-class AmountTest extends Specification {
-
-    def "toString() retains original measurement"() {
-        expect:
-        Amount.valueOf(value, units).toString() == str
-
-        where:
-        value    | units            | str
-        0        | Fruit.apples     | "0 apples"
-        1        | Fruit.apples     | "1 apple"
-        1000     | Fruit.apples     | "1000 apples"
-        0.123    | Fruit.apples     | "0.123 apples"
-        0.333333 | Fruit.apples     | "0.333333 apples"
-        0.5555   | Fruit.apples     | "0.5555 apples"
-        -12      | Fruit.apples     | "-12 apples"
-        145      | Fruit.oranges    | "145 oranges"
-        0.23     | Fruit.grapefruit | "0.23 grapefruit"
-    }
-
-    def "format() displays amount in highest possible units with rounded value"() {
-        expect:
-        Amount.valueOf(value, units).format() == str
-
-        where:
-        value    | units         | str
-        0        | Fruit.apples  | "0 apples"
-        1        | Fruit.apples  | "1 apple"
-        0.032    | Fruit.apples  | "0.032 apples"
-        2.367722 | Fruit.apples  | "2.368 apples"
-        3        | Fruit.apples  | "1 orange"
-        5        | Fruit.apples  | "1 grapefruit"
-        4        | Fruit.apples  | "1.333 oranges"
-        1000     | Fruit.oranges | "600 grapefruit"
-        42       | Fruit.oranges | "25.2 grapefruit"
-        0.45     | Fruit.oranges | "1.35 apples"
-        -12      | Fruit.apples  | "-2.4 grapefruit"
-    }
-
-    def "can convert to specific units"() {
-        expect:
-        Amount.valueOf(value, fromUnits).toUnits(toUnits) == Amount.valueOf(converted, toUnits)
-        Amount.valueOf(value, fromUnits).toUnits(toUnits).toString() == Amount.valueOf(converted, toUnits).toString()
-
-        where:
-        value | fromUnits        | toUnits          | converted
-        21    | Fruit.apples     | Fruit.apples     | 21
-        2.1   | Fruit.oranges    | Fruit.apples     | 6.3
-        1024  | Fruit.grapefruit | Fruit.apples     | 1024 * 5
-        1     | Fruit.apples     | Fruit.grapefruit | 0.2
-        1     | Fruit.apples     | Fruit.oranges    | 0.333333
-    }
-
-    def "amounts are equal when normalised values are the same"() {
-        expect:
-        def a = Amount.valueOf(valueA, unitsA)
-        def b = Amount.valueOf(valueB, unitsB)
-        a.equals(b)
-        b.equals(a)
-        a.hashCode() == b.hashCode()
-        a.compareTo(b) == 0
-        b.compareTo(a) == 0
-
-        where:
-        valueA  | unitsA        | valueB  | unitsB
-        0       | Fruit.apples  | 0       | Fruit.apples
-        0       | Fruit.apples  | 0       | Fruit.oranges
-        9       | Fruit.apples  | 3       | Fruit.oranges
-        5       | Fruit.oranges | 3       | Fruit.grapefruit
-        6.3399  | Fruit.apples  | 2.1133  | Fruit.oranges
-        0.333   | Fruit.apples  | 0.333   | Fruit.apples
-        0.55667 | Fruit.apples  | 0.55667 | Fruit.apples
-    }
-
-    def "amounts are not equal when normalised values are different"() {
-        expect:
-        def a = Amount.valueOf(valueA, unitsA)
-        def b = Amount.valueOf(valueB, unitsB)
-        !a.equals(b)
-        !b.equals(a)
-        a.hashCode() != b.hashCode()
-        a.compareTo(b) != 0
-        b.compareTo(a) != 0
-
-        where:
-        valueA | unitsA           | valueB  | unitsB
-        0      | Fruit.apples     | 1       | Fruit.apples
-        0.334  | Fruit.apples     | 0.333   | Fruit.apples
-        0.334  | Fruit.apples     | 0.33433 | Fruit.apples
-        1      | Fruit.apples     | 1       | Fruit.oranges
-        1      | Fruit.grapefruit | 1       | Fruit.oranges
-    }
-
-    def "can compare values"() {
-        expect:
-        def a = Amount.valueOf(valueA, unitsA)
-        def b = Amount.valueOf(valueB, unitsB)
-        a < b
-        b > a
-        a != b
-        b != a
-
-        where:
-        valueA  | unitsA        | valueB | unitsB
-        0.1     | Fruit.oranges | 0.101  | Fruit.oranges
-        0.33333 | Fruit.oranges | 1      | Fruit.apples
-        1       | Fruit.apples  | 1      | Fruit.oranges
-        1       | Fruit.oranges | 1      | Fruit.grapefruit
-        -1      | Fruit.apples  | 0      | Fruit.oranges
-        5.9     | Fruit.apples  | 2      | Fruit.oranges
-    }
-
-    def "can add amounts with same units"() {
-        expect:
-        Amount.valueOf(a, units) + Amount.valueOf(b, units) == Amount.valueOf(c, units)
-
-        where:
-        a    | b     | c      | units
-        0    | 0     | 0      | Fruit.apples
-        1    | 2     | 3      | Fruit.apples
-        1    | 2     | 3      | Fruit.oranges
-        23.4 | 0.567 | 23.967 | Fruit.apples
-    }
-
-    def "can add amounts with different units of same quantity"() {
-        def sum = Amount.valueOf(valueA, unitsA) + Amount.valueOf(valueB, unitsB)
-        expect:
-        sum == Amount.valueOf(valueC, unitsC)
-        sum.toString() == Amount.valueOf(valueC, unitsC).toString()
-
-        where:
-        valueA | unitsA        | valueB | unitsB        | valueC  | unitsC
-        0      | Fruit.apples  | 0      | Fruit.oranges | 0       | Fruit.apples
-        0      | Fruit.apples  | 12     | Fruit.oranges | 12      | Fruit.oranges
-        0.7    | Fruit.apples  | 0      | Fruit.oranges | 0.7     | Fruit.apples
-        1      | Fruit.apples  | 2      | Fruit.oranges | 7       | Fruit.apples
-        3      | Fruit.oranges | 4      | Fruit.oranges | 7       | Fruit.oranges
-        12.45  | Fruit.apples  | 0.2222 | Fruit.oranges | 13.1166 | Fruit.apples
-    }
-
-    def "can subtract amounts with same units"() {
-        expect:
-        Amount.valueOf(a, units) - Amount.valueOf(b, units) == Amount.valueOf(c, units)
-
-        where:
-        a    | b     | c      | units
-        0    | 0     | 0      | Fruit.apples
-        2    | 1     | 1      | Fruit.apples
-        123  | 12    | 111    | Fruit.oranges
-        10   | 56    | -46    | Fruit.apples
-        23.4 | 0.567 | 22.833 | Fruit.apples
-    }
-
-    def "can subtract amounts with different units of same quantity"() {
-        def difference = Amount.valueOf(valueA, unitsA) - Amount.valueOf(valueB, unitsB)
-        expect:
-        difference == Amount.valueOf(valueC, unitsC)
-        difference.toString() == Amount.valueOf(valueC, unitsC).toString()
-
-        where:
-        valueA | unitsA        | valueB | unitsB        | valueC  | unitsC
-        0      | Fruit.apples  | 0      | Fruit.oranges | 0       | Fruit.apples
-        1      | Fruit.oranges | 0      | Fruit.apples  | 1       | Fruit.oranges
-        4      | Fruit.apples  | 1      | Fruit.oranges | 1       | Fruit.apples
-        2      | Fruit.apples  | 1      | Fruit.oranges | -1      | Fruit.apples
-        0      | Fruit.apples  | 12     | Fruit.oranges | -36     | Fruit.apples
-        0.7    | Fruit.apples  | 0      | Fruit.oranges | 0.7     | Fruit.apples
-        0.7    | Fruit.oranges | 1      | Fruit.apples  | 1.1     | Fruit.apples
-        12.45  | Fruit.apples  | 0.2222 | Fruit.oranges | 11.7834 | Fruit.apples
-    }
-
-    def "can divide amount by a unitless value"() {
-        expect:
-        Amount.valueOf(a, Fruit.apples) / b == Amount.valueOf(c, Fruit.apples)
-
-        where:
-        a  | b   | c
-        0  | 200 | 0
-        2  | 1   | 2
-        5  | 10  | 0.5
-        10 | -2  | -5
-        1  | 3   | 0.333333
-        2  | 3   | 0.666667
-    }
-
-    def "can divide amount by another amount of same quantity"() {
-        expect:
-        Amount.valueOf(valueA, unitsA) / Amount.valueOf(valueB, unitsB) == result
-
-        where:
-        valueA | unitsA        | valueB | unitsB           | result
-        0      | Fruit.apples  | 200    | Fruit.apples     | 0
-        2      | Fruit.apples  | 1      | Fruit.apples     | 2
-        5      | Fruit.apples  | 10     | Fruit.apples     | 0.5
-        10     | Fruit.apples  | -2     | Fruit.apples     | -5
-        1      | Fruit.apples  | 3      | Fruit.apples     | 0.333333
-        2      | Fruit.apples  | 3      | Fruit.apples     | 0.666667
-        4      | Fruit.apples  | 1.2    | Fruit.oranges    | 1.111111
-        125    | Fruit.oranges | 23.4   | Fruit.grapefruit | 3.205128
-    }
-
-    def "can convert to absolute value"() {
-        expect:
-        Amount.valueOf(12, Fruit.grapefruit).abs() == Amount.valueOf(12, Fruit.grapefruit)
-        Amount.valueOf(-34, Fruit.grapefruit).abs() == Amount.valueOf(34, Fruit.grapefruit)
-    }
-
-    public static class Fruit {
-        static def apples = Units.base(Fruit.class, "apple", "apples")
-        static def oranges = apples.times(3, "orange", "oranges")
-        static def grapefruit = apples.times(5, "grapefruit")
-    }
-}
diff --git a/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/DurationTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/DurationTest.groovy
deleted file mode 100644
index f98d309..0000000
--- a/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/DurationTest.groovy
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.peformance.fixture
-
-import spock.lang.Specification
-
-class DurationTest extends Specification {
-    def "millisecond durations have useful string formats"() {
-        expect:
-        Duration.millis(millis).toString() == str
-        Duration.millis(millis).format() == formatted
-
-        where:
-        millis  | str          | formatted
-        0       | "0 ms"       | "0 ms"
-        1       | "1 ms"       | "1 ms"
-        0.123   | "0.123 ms"   | "0.123 ms"
-        -12     | "-12 ms"     | "-12 ms"
-        1000    | "1000 ms"    | "1 s"
-        123456  | "123456 ms"  | "2.058 m"
-        3607000 | "3607000 ms" | "1.002 h"
-    }
-
-    def "second durations have useful string formats"() {
-        expect:
-        Duration.seconds(millis).toString() == str
-
-        where:
-        millis    | str           | format
-        0         | "0 s"         | "0 ms"
-        1         | "1 s"         | "1 s"
-        1000      | "1000 s"      | "16.667 m"
-        0.123     | "0.123 s"     | "123 ms"
-        0.1234567 | "0.1234567 s" | "123.457 ms"
-        -12       | "-12 s"       | "-12 s"
-    }
-
-    def "can convert between units"() {
-        expect:
-        Duration.millis(45000) == Duration.seconds(45)
-        Duration.seconds(0.98) == Duration.millis(980)
-        Duration.seconds(120) == Duration.minutes(2)
-        Duration.seconds(30) == Duration.minutes(0.5)
-        Duration.hours(30) == Duration.millis(30 * 60 * 60 * 1000)
-    }
-}
diff --git a/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/PerformanceResultsTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/PerformanceResultsTest.groovy
deleted file mode 100644
index 3f62364..0000000
--- a/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/PerformanceResultsTest.groovy
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.peformance.fixture
-
-import spock.lang.Specification
-
-class PerformanceResultsTest extends Specification {
-    def PerformanceResults result = new PerformanceResults()
-
-    def "passes when average execution time for current release is smaller than average execution time for previous release"() {
-        given:
-        result.previous.add(operation(executionTime: 110))
-        result.previous.add(operation(executionTime: 100))
-        result.previous.add(operation(executionTime: 90))
-
-        and:
-        result.current.add(operation(executionTime: 90))
-        result.current.add(operation(executionTime: 110))
-        result.current.add(operation(executionTime: 90))
-
-        expect:
-        result.assertCurrentVersionHasNotRegressed()
-    }
-
-    def "passes when average execution time for current release is within specified range of average execution time for previous release"() {
-        given:
-        result.maxExecutionTimeRegression = Duration.millis(10)
-        result.previous.add(operation(executionTime: 100))
-        result.previous.add(operation(executionTime: 100))
-        result.previous.add(operation(executionTime: 100))
-
-        and:
-        result.current.add(operation(executionTime: 110))
-        result.current.add(operation(executionTime: 110))
-        result.current.add(operation(executionTime: 110))
-
-        expect:
-        result.assertCurrentVersionHasNotRegressed()
-    }
-
-    def "fails when average execution time for current release is larger than average execution time for previous release"() {
-        given:
-        result.displayName = '<test>'
-        result.maxExecutionTimeRegression = Duration.millis(10)
-        result.previous.add(operation(executionTime: 100))
-        result.previous.add(operation(executionTime: 100))
-        result.previous.add(operation(executionTime: 100))
-
-        and:
-        result.current.add(operation(executionTime: 110))
-        result.current.add(operation(executionTime: 110))
-        result.current.add(operation(executionTime: 111))
-
-        when:
-        result.assertCurrentVersionHasNotRegressed()
-
-        then:
-        AssertionError e = thrown()
-        e.message.startsWith('Speed <test>: current Gradle is a little slower on average.')
-        e.message.contains('Difference: 10.333 ms slower (10.333 ms), 10.33%')
-    }
-
-    def "passes when average heap usage for current release is smaller than average heap usage for previous release"() {
-        given:
-        result.previous.add(operation(heapUsed: 1000))
-        result.previous.add(operation(heapUsed: 1000))
-        result.previous.add(operation(heapUsed: 1000))
-
-        and:
-        result.current.add(operation(heapUsed: 1000))
-        result.current.add(operation(heapUsed: 1005))
-        result.current.add(operation(heapUsed: 994))
-
-        expect:
-        result.assertCurrentVersionHasNotRegressed()
-    }
-
-    def "passes when average heap usage for current release is slightly larger than average heap usage for previous release"() {
-        given:
-        result.maxMemoryRegression = DataAmount.bytes(100)
-        result.previous.add(operation(heapUsed: 1000))
-        result.previous.add(operation(heapUsed: 1000))
-        result.previous.add(operation(heapUsed: 1000))
-
-        and:
-        result.current.add(operation(heapUsed: 1100))
-        result.current.add(operation(heapUsed: 1100))
-        result.current.add(operation(heapUsed: 1100))
-
-        expect:
-        result.assertCurrentVersionHasNotRegressed()
-    }
-
-    def "fails when average heap usage for current release is larger than average heap usage for previous release"() {
-        given:
-        result.displayName = '<test>'
-        result.maxMemoryRegression = DataAmount.bytes(100)
-        result.previous.add(operation(heapUsed: 1000))
-        result.previous.add(operation(heapUsed: 1000))
-        result.previous.add(operation(heapUsed: 1000))
-
-        and:
-        result.current.add(operation(heapUsed: 1100))
-        result.current.add(operation(heapUsed: 1100))
-        result.current.add(operation(heapUsed: 1101))
-
-        when:
-        result.assertCurrentVersionHasNotRegressed()
-
-        then:
-        AssertionError e = thrown()
-        e.message.startsWith('Memory <test>: current Gradle needs a little more memory on average.')
-        e.message.contains('Difference: 100.333 B more (100.333 B), 10.03%')
-    }
-
-    def "fails when both heap usage and execution time have regressed"() {
-        given:
-        result.displayName = '<test>'
-        result.previous.add(operation(heapUsed: 1000, executionTime: 100))
-        result.previous.add(operation(heapUsed: 1000, executionTime: 100))
-        result.previous.add(operation(heapUsed: 1000, executionTime: 100))
-
-        and:
-        result.current.add(operation(heapUsed: 1100, executionTime: 110))
-        result.current.add(operation(heapUsed: 1100, executionTime: 110))
-        result.current.add(operation(heapUsed: 1101, executionTime: 111))
-
-        when:
-        result.assertCurrentVersionHasNotRegressed()
-
-        then:
-        AssertionError e = thrown()
-        e.message.contains('Speed <test>: current Gradle is a little slower on average.')
-        e.message.contains('Difference: 10.333 ms slower (10.333 ms)')
-        e.message.contains('Memory <test>: current Gradle needs a little more memory on average.')
-        e.message.contains('Difference: 100.333 B more (100.333 B)')
-    }
-
-    def "fails when a previous operation fails"() {
-        given:
-        result.previous.add(operation(failure: new RuntimeException()))
-        result.current.add(operation())
-
-        when:
-        result.assertCurrentVersionHasNotRegressed()
-
-        then:
-        AssertionError e = thrown()
-        e.message.startsWith("Some builds have failed.")
-    }
-
-    def "fails when a current operation fails"() {
-        given:
-        result.previous.add(operation())
-        result.current.add(operation(failure: new RuntimeException()))
-
-        when:
-        result.assertCurrentVersionHasNotRegressed()
-
-        then:
-        AssertionError e = thrown()
-        e.message.startsWith("Some builds have failed.")
-    }
-
-    def "fails when an operation fails"() {
-        given:
-        result.others.oldVersion = new MeasuredOperationList()
-        result.previous.add(operation())
-        result.current.add(operation())
-        result.others.oldVersion.add(operation(failure: new RuntimeException()))
-
-        when:
-        result.assertCurrentVersionHasNotRegressed()
-
-        then:
-        AssertionError e = thrown()
-        e.message.startsWith("Some builds have failed.")
-    }
-
-    private MeasuredOperation operation(Map<String, Object> args) {
-        def operation = new MeasuredOperation()
-        operation.executionTime = Duration.millis(args?.executionTime ?: 120)
-        operation.totalMemoryUsed = DataAmount.bytes(args?.heapUsed ?: 1024)
-        operation.exception = args?.failure
-        return operation
-    }
-
-}
diff --git a/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/PrettyCalculatorSpec.groovy b/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/PrettyCalculatorSpec.groovy
deleted file mode 100644
index 4435b0c..0000000
--- a/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/PrettyCalculatorSpec.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.peformance.fixture
-
-import spock.lang.Specification
-
-import static org.gradle.peformance.fixture.PrettyCalculator.percentChange
-
-/**
- * by Szczepan Faber, created at: 10/30/12
- */
-class PrettyCalculatorSpec extends Specification {
-
-    def "knows percentage change"() {
-        expect:
-        percentChange(Amount.valueOf(current, Duration.SECONDS), Amount.valueOf(prevous, Duration.SECONDS)) == percent
-
-        where:
-        current  | prevous | percent
-        1        | 1       | 0
-        3        | 1       | 200
-        1        | 3       | -66.67
-        2        | 3       | -33.33
-        5        | 4       | 25
-        2.2      | 3.567   | -38.32
-        12.00001 | 10.23   | 17.30
-        //not strictly true, but for our purposes should do:
-        0        | 3       | -100
-        300      | 0       | 100
-    }
-}
\ No newline at end of file
diff --git a/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/UnitsTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/UnitsTest.groovy
deleted file mode 100644
index 98c8509..0000000
--- a/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/UnitsTest.groovy
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.peformance.fixture
-
-import spock.lang.Specification
-
-class UnitsTest extends Specification {
-    def "can compare units of same quantity"() {
-        def base = Units.base(Void.class, "base")
-        def units1 = base.times(12, "units1")
-        def units2 = base.times(33, "units2")
-
-        expect:
-        base < units1
-        base < units2
-        units1 > base
-        units1 < units2
-        units2 > base
-        units2 > units1
-    }
-}
diff --git a/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/AmountTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/AmountTest.groovy
new file mode 100644
index 0000000..5aa2e86
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/AmountTest.groovy
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.performance.fixture
+
+import spock.lang.Specification
+
+class AmountTest extends Specification {
+
+    def "toString() retains original measurement"() {
+        expect:
+        Amount.valueOf(value, units).toString() == str
+
+        where:
+        value    | units            | str
+        0        | Fruit.apples     | "0 apples"
+        1        | Fruit.apples     | "1 apple"
+        1000     | Fruit.apples     | "1000 apples"
+        0.123    | Fruit.apples     | "0.123 apples"
+        0.333333 | Fruit.apples     | "0.333333 apples"
+        0.5555   | Fruit.apples     | "0.5555 apples"
+        -12      | Fruit.apples     | "-12 apples"
+        145      | Fruit.oranges    | "145 oranges"
+        0.23     | Fruit.grapefruit | "0.23 grapefruit"
+    }
+
+    def "format() displays amount in highest possible units with rounded value"() {
+        expect:
+        Amount.valueOf(value, units).format() == str
+
+        where:
+        value    | units         | str
+        0        | Fruit.apples  | "0 apples"
+        1        | Fruit.apples  | "1 apple"
+        0.032    | Fruit.apples  | "0.032 apples"
+        2.367722 | Fruit.apples  | "2.368 apples"
+        3        | Fruit.apples  | "1 orange"
+        5        | Fruit.apples  | "1 grapefruit"
+        4        | Fruit.apples  | "1.333 oranges"
+        1000     | Fruit.oranges | "600 grapefruit"
+        42       | Fruit.oranges | "25.2 grapefruit"
+        0.45     | Fruit.oranges | "1.35 apples"
+        -12      | Fruit.apples  | "-2.4 grapefruit"
+    }
+
+    def "can convert to specific units"() {
+        expect:
+        Amount.valueOf(value, fromUnits).toUnits(toUnits) == Amount.valueOf(converted, toUnits)
+        Amount.valueOf(value, fromUnits).toUnits(toUnits).toString() == Amount.valueOf(converted, toUnits).toString()
+
+        where:
+        value | fromUnits        | toUnits          | converted
+        21    | Fruit.apples     | Fruit.apples     | 21
+        2.1   | Fruit.oranges    | Fruit.apples     | 6.3
+        1024  | Fruit.grapefruit | Fruit.apples     | 1024 * 5
+        1     | Fruit.apples     | Fruit.grapefruit | 0.2
+        1     | Fruit.apples     | Fruit.oranges    | 0.333333
+    }
+
+    def "amounts are equal when normalised values are the same"() {
+        expect:
+        def a = Amount.valueOf(valueA, unitsA)
+        def b = Amount.valueOf(valueB, unitsB)
+        a.equals(b)
+        b.equals(a)
+        a.hashCode() == b.hashCode()
+        a.compareTo(b) == 0
+        b.compareTo(a) == 0
+
+        where:
+        valueA  | unitsA        | valueB  | unitsB
+        0       | Fruit.apples  | 0       | Fruit.apples
+        0       | Fruit.apples  | 0       | Fruit.oranges
+        9       | Fruit.apples  | 3       | Fruit.oranges
+        5       | Fruit.oranges | 3       | Fruit.grapefruit
+        6.3399  | Fruit.apples  | 2.1133  | Fruit.oranges
+        0.333   | Fruit.apples  | 0.333   | Fruit.apples
+        0.55667 | Fruit.apples  | 0.55667 | Fruit.apples
+    }
+
+    def "amounts are not equal when normalised values are different"() {
+        expect:
+        def a = Amount.valueOf(valueA, unitsA)
+        def b = Amount.valueOf(valueB, unitsB)
+        !a.equals(b)
+        !b.equals(a)
+        a.hashCode() != b.hashCode()
+        a.compareTo(b) != 0
+        b.compareTo(a) != 0
+
+        where:
+        valueA | unitsA           | valueB  | unitsB
+        0      | Fruit.apples     | 1       | Fruit.apples
+        0.334  | Fruit.apples     | 0.333   | Fruit.apples
+        0.334  | Fruit.apples     | 0.33433 | Fruit.apples
+        1      | Fruit.apples     | 1       | Fruit.oranges
+        1      | Fruit.grapefruit | 1       | Fruit.oranges
+    }
+
+    def "can compare values"() {
+        expect:
+        def a = Amount.valueOf(valueA, unitsA)
+        def b = Amount.valueOf(valueB, unitsB)
+        a < b
+        b > a
+        a != b
+        b != a
+
+        where:
+        valueA  | unitsA        | valueB | unitsB
+        0.1     | Fruit.oranges | 0.101  | Fruit.oranges
+        0.33333 | Fruit.oranges | 1      | Fruit.apples
+        1       | Fruit.apples  | 1      | Fruit.oranges
+        1       | Fruit.oranges | 1      | Fruit.grapefruit
+        -1      | Fruit.apples  | 0      | Fruit.oranges
+        5.9     | Fruit.apples  | 2      | Fruit.oranges
+    }
+
+    def "can add amounts with same units"() {
+        expect:
+        Amount.valueOf(a, units) + Amount.valueOf(b, units) == Amount.valueOf(c, units)
+
+        where:
+        a    | b     | c      | units
+        0    | 0     | 0      | Fruit.apples
+        1    | 2     | 3      | Fruit.apples
+        1    | 2     | 3      | Fruit.oranges
+        23.4 | 0.567 | 23.967 | Fruit.apples
+    }
+
+    def "can add amounts with different units of same quantity"() {
+        def sum = Amount.valueOf(valueA, unitsA) + Amount.valueOf(valueB, unitsB)
+        expect:
+        sum == Amount.valueOf(valueC, unitsC)
+        sum.toString() == Amount.valueOf(valueC, unitsC).toString()
+
+        where:
+        valueA | unitsA        | valueB | unitsB        | valueC  | unitsC
+        0      | Fruit.apples  | 0      | Fruit.oranges | 0       | Fruit.apples
+        0      | Fruit.apples  | 12     | Fruit.oranges | 12      | Fruit.oranges
+        0.7    | Fruit.apples  | 0      | Fruit.oranges | 0.7     | Fruit.apples
+        1      | Fruit.apples  | 2      | Fruit.oranges | 7       | Fruit.apples
+        3      | Fruit.oranges | 4      | Fruit.oranges | 7       | Fruit.oranges
+        12.45  | Fruit.apples  | 0.2222 | Fruit.oranges | 13.1166 | Fruit.apples
+    }
+
+    def "can subtract amounts with same units"() {
+        expect:
+        Amount.valueOf(a, units) - Amount.valueOf(b, units) == Amount.valueOf(c, units)
+
+        where:
+        a    | b     | c      | units
+        0    | 0     | 0      | Fruit.apples
+        2    | 1     | 1      | Fruit.apples
+        123  | 12    | 111    | Fruit.oranges
+        10   | 56    | -46    | Fruit.apples
+        23.4 | 0.567 | 22.833 | Fruit.apples
+    }
+
+    def "can subtract amounts with different units of same quantity"() {
+        def difference = Amount.valueOf(valueA, unitsA) - Amount.valueOf(valueB, unitsB)
+        expect:
+        difference == Amount.valueOf(valueC, unitsC)
+        difference.toString() == Amount.valueOf(valueC, unitsC).toString()
+
+        where:
+        valueA | unitsA        | valueB | unitsB        | valueC  | unitsC
+        0      | Fruit.apples  | 0      | Fruit.oranges | 0       | Fruit.apples
+        1      | Fruit.oranges | 0      | Fruit.apples  | 1       | Fruit.oranges
+        4      | Fruit.apples  | 1      | Fruit.oranges | 1       | Fruit.apples
+        2      | Fruit.apples  | 1      | Fruit.oranges | -1      | Fruit.apples
+        0      | Fruit.apples  | 12     | Fruit.oranges | -36     | Fruit.apples
+        0.7    | Fruit.apples  | 0      | Fruit.oranges | 0.7     | Fruit.apples
+        0.7    | Fruit.oranges | 1      | Fruit.apples  | 1.1     | Fruit.apples
+        12.45  | Fruit.apples  | 0.2222 | Fruit.oranges | 11.7834 | Fruit.apples
+    }
+
+    def "can divide amount by a unitless value"() {
+        expect:
+        Amount.valueOf(a, Fruit.apples) / b == Amount.valueOf(c, Fruit.apples)
+
+        where:
+        a  | b   | c
+        0  | 200 | 0
+        2  | 1   | 2
+        5  | 10  | 0.5
+        10 | -2  | -5
+        1  | 3   | 0.333333
+        2  | 3   | 0.666667
+    }
+
+    def "can divide amount by another amount of same quantity"() {
+        expect:
+        Amount.valueOf(valueA, unitsA) / Amount.valueOf(valueB, unitsB) == result
+
+        where:
+        valueA | unitsA        | valueB | unitsB           | result
+        0      | Fruit.apples  | 200    | Fruit.apples     | 0
+        2      | Fruit.apples  | 1      | Fruit.apples     | 2
+        5      | Fruit.apples  | 10     | Fruit.apples     | 0.5
+        10     | Fruit.apples  | -2     | Fruit.apples     | -5
+        1      | Fruit.apples  | 3      | Fruit.apples     | 0.333333
+        2      | Fruit.apples  | 3      | Fruit.apples     | 0.666667
+        4      | Fruit.apples  | 1.2    | Fruit.oranges    | 1.111111
+        125    | Fruit.oranges | 23.4   | Fruit.grapefruit | 3.205128
+    }
+
+    def "can convert to absolute value"() {
+        expect:
+        Amount.valueOf(12, Fruit.grapefruit).abs() == Amount.valueOf(12, Fruit.grapefruit)
+        Amount.valueOf(-34, Fruit.grapefruit).abs() == Amount.valueOf(34, Fruit.grapefruit)
+    }
+
+    public static class Fruit {
+        static def apples = Units.base(Fruit.class, "apple", "apples")
+        static def oranges = apples.times(3, "orange", "oranges")
+        static def grapefruit = apples.times(5, "grapefruit")
+    }
+}
diff --git a/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/DurationTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/DurationTest.groovy
new file mode 100644
index 0000000..f6e7b99
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/DurationTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.performance.fixture
+
+import spock.lang.Specification
+
+class DurationTest extends Specification {
+    def "millisecond durations have useful string formats"() {
+        expect:
+        Duration.millis(millis).toString() == str
+        Duration.millis(millis).format() == formatted
+
+        where:
+        millis  | str          | formatted
+        0       | "0 ms"       | "0 ms"
+        1       | "1 ms"       | "1 ms"
+        0.123   | "0.123 ms"   | "0.123 ms"
+        -12     | "-12 ms"     | "-12 ms"
+        1000    | "1000 ms"    | "1 s"
+        123456  | "123456 ms"  | "2.058 m"
+        3607000 | "3607000 ms" | "1.002 h"
+    }
+
+    def "second durations have useful string formats"() {
+        expect:
+        Duration.seconds(millis).toString() == str
+
+        where:
+        millis    | str           | format
+        0         | "0 s"         | "0 ms"
+        1         | "1 s"         | "1 s"
+        1000      | "1000 s"      | "16.667 m"
+        0.123     | "0.123 s"     | "123 ms"
+        0.1234567 | "0.1234567 s" | "123.457 ms"
+        -12       | "-12 s"       | "-12 s"
+    }
+
+    def "can convert between units"() {
+        expect:
+        Duration.millis(45000) == Duration.seconds(45)
+        Duration.seconds(0.98) == Duration.millis(980)
+        Duration.seconds(120) == Duration.minutes(2)
+        Duration.seconds(30) == Duration.minutes(0.5)
+        Duration.hours(30) == Duration.millis(30 * 60 * 60 * 1000)
+    }
+}
diff --git a/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/PerformanceResultsTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/PerformanceResultsTest.groovy
new file mode 100644
index 0000000..30bf52d
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/PerformanceResultsTest.groovy
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture
+
+import spock.lang.Specification
+
+import static org.gradle.performance.fixture.BaselineVersion.baseline
+
+class PerformanceResultsTest extends Specification {
+    def PerformanceResults result = new PerformanceResults()
+
+    def "passes when average execution time for current release is smaller than average execution time for previous releases"() {
+        given:
+        result.baselineVersions[0].results.add(operation(executionTime: 110))
+        result.baselineVersions[0].results.add(operation(executionTime: 100))
+        result.baselineVersions[0].results.add(operation(executionTime: 90))
+
+        and:
+        result.current.add(operation(executionTime: 90))
+        result.current.add(operation(executionTime: 110))
+        result.current.add(operation(executionTime: 90))
+
+        expect:
+        result.assertCurrentVersionHasNotRegressed()
+    }
+
+    def "passes when average execution time for current release is within specified range of average execution time for previous releases"() {
+        given:
+        result.baselineVersions[0].maxExecutionTimeRegression = Duration.millis(10)
+        result.baselineVersions[0].results << operation(executionTime: 100)
+        result.baselineVersions[0].results << operation(executionTime: 100)
+        result.baselineVersions[0].results << operation(executionTime: 100)
+
+        result.baselineVersions << baseline("1.3")
+        result.baselineVersions[1].results << operation(executionTime: 115)
+        result.baselineVersions[1].results << operation(executionTime: 105)
+        result.baselineVersions[1].results << operation(executionTime: 110)
+
+        and:
+        result.current << operation(executionTime: 110)
+        result.current << operation(executionTime: 110)
+        result.current << operation(executionTime: 110)
+
+        expect:
+        result.assertCurrentVersionHasNotRegressed()
+    }
+
+    def "fails when average execution time for current release is larger than average execution time for previous releases"() {
+        given:
+        result.displayName = '<test>'
+
+        result.baselineVersions[0].version = '1.2' //fail
+        result.baselineVersions[0].maxExecutionTimeRegression = Duration.millis(10)
+        result.baselineVersions[0].results << operation(executionTime: 100)
+        result.baselineVersions[0].results << operation(executionTime: 100)
+        result.baselineVersions[0].results << operation(executionTime: 100)
+
+        result.baselineVersions << baseline("1.3") //pass
+        result.baselineVersions[1].maxExecutionTimeRegression = Duration.millis(10)
+        result.baselineVersions[1].results << operation(executionTime: 101)
+        result.baselineVersions[1].results << operation(executionTime: 100)
+        result.baselineVersions[1].results << operation(executionTime: 100)
+
+        and:
+        result.current << operation(executionTime: 110)
+        result.current << operation(executionTime: 110)
+        result.current << operation(executionTime: 111)
+
+        when:
+        result.assertCurrentVersionHasNotRegressed()
+
+        then:
+        AssertionError e = thrown()
+        e.message.startsWith("Speed <test>: we're slower than 1.2.")
+        e.message.contains('Difference: 10.333 ms slower (10.333 ms), 10.33%')
+        !e.message.contains('1.3')
+    }
+
+    def "passes when average heap usage for current release is smaller than average heap usage for previous releases"() {
+        given:
+        result.baselineVersions[0].results << operation(heapUsed: 1000)
+        result.baselineVersions[0].results << operation(heapUsed: 1000)
+        result.baselineVersions[0].results << operation(heapUsed: 1000)
+
+        result.baselineVersions << baseline("1.3")
+        result.baselineVersions[1].results << operation(heapUsed: 800)
+        result.baselineVersions[1].results << operation(heapUsed: 1000)
+        result.baselineVersions[1].results << operation(heapUsed: 1200)
+
+        and:
+        result.current << operation(heapUsed: 1000)
+        result.current << operation(heapUsed: 1005)
+        result.current << operation(heapUsed: 994)
+
+        expect:
+        result.assertCurrentVersionHasNotRegressed()
+    }
+
+    def "passes when average heap usage for current release is slightly larger than average heap usage for previous releases"() {
+        given:
+        result.baselineVersions[0].maxMemoryRegression = DataAmount.bytes(100)
+        result.baselineVersions[0].results << operation(heapUsed: 1000)
+        result.baselineVersions[0].results << operation(heapUsed: 1000)
+        result.baselineVersions[0].results << operation(heapUsed: 1000)
+
+        result.baselineVersions << baseline("1.3")
+        result.baselineVersions[1].maxMemoryRegression = DataAmount.bytes(100)
+        result.baselineVersions[1].results << operation(heapUsed: 900)
+        result.baselineVersions[1].results << operation(heapUsed: 1000)
+        result.baselineVersions[1].results << operation(heapUsed: 1100)
+
+        and:
+        result.current << operation(heapUsed: 1100)
+        result.current << operation(heapUsed: 1100)
+        result.current << operation(heapUsed: 1100)
+
+        expect:
+        result.assertCurrentVersionHasNotRegressed()
+    }
+
+    def "fails when average heap usage for current release is larger than average heap usage for previous releases"() {
+        given:
+        result.displayName = '<test>'
+
+        result.baselineVersions[0].version = "1.1" //pass
+        result.baselineVersions[0].maxMemoryRegression = DataAmount.bytes(100)
+        result.baselineVersions[0].results << operation(heapUsed: 1001)
+        result.baselineVersions[0].results << operation(heapUsed: 1001)
+        result.baselineVersions[0].results << operation(heapUsed: 1001)
+
+        result.baselineVersions << baseline("1.2") //fail
+        result.baselineVersions[1].maxMemoryRegression = DataAmount.bytes(100)
+        result.baselineVersions[1].results << operation(heapUsed: 1000)
+        result.baselineVersions[1].results << operation(heapUsed: 1000)
+        result.baselineVersions[1].results << operation(heapUsed: 1000)
+
+        and:
+        result.current << operation(heapUsed: 1100)
+        result.current << operation(heapUsed: 1100)
+        result.current << operation(heapUsed: 1101)
+
+        when:
+        result.assertCurrentVersionHasNotRegressed()
+
+        then:
+        AssertionError e = thrown()
+        e.message.startsWith('Memory <test>: we need more memory than 1.2.')
+        e.message.contains('Difference: 100.333 B more (100.333 B), 10.03%')
+        !e.message.contains('1.1')
+    }
+
+    def "fails when both heap usage and execution time have regressed"() {
+        given:
+        result.displayName = '<test>'
+        result.baselineVersions[0].version = '1.1' //pass
+        result.baselineVersions[0].results << operation(heapUsed: 1200, executionTime: 150)
+        result.baselineVersions[0].results << operation(heapUsed: 1000, executionTime: 100)
+        result.baselineVersions[0].results << operation(heapUsed: 1200, executionTime: 150)
+
+        result.baselineVersions << baseline("1.2") //fail
+        result.baselineVersions[1].results << operation(heapUsed: 1000, executionTime: 100)
+        result.baselineVersions[1].results << operation(heapUsed: 1000, executionTime: 100)
+        result.baselineVersions[1].results << operation(heapUsed: 1000, executionTime: 100)
+
+        and:
+        result.current << operation(heapUsed: 1100, executionTime: 110)
+        result.current << operation(heapUsed: 1100, executionTime: 110)
+        result.current << operation(heapUsed: 1101, executionTime: 111)
+
+        when:
+        result.assertCurrentVersionHasNotRegressed()
+
+        then:
+        AssertionError e = thrown()
+        e.message.contains("Speed <test>: we're slower than 1.2.")
+        e.message.contains('Difference: 10.333 ms slower (10.333 ms)')
+        e.message.contains('Memory <test>: we need more memory than 1.2.')
+        e.message.contains('Difference: 100.333 B more (100.333 B)')
+        !e.message.contains('1.1')
+    }
+
+    def "fails when a previous operation fails"() {
+        given:
+        result.baselineVersions[0].results << operation(failure: new RuntimeException())
+        result.current.add(operation())
+
+        when:
+        result.assertCurrentVersionHasNotRegressed()
+
+        then:
+        AssertionError e = thrown()
+        e.message.startsWith("Some builds have failed.")
+    }
+
+    def "fails when a current operation fails"() {
+        given:
+        result.baselineVersions[0].results << operation()
+        result.current.add(operation(failure: new RuntimeException()))
+
+        when:
+        result.assertCurrentVersionHasNotRegressed()
+
+        then:
+        AssertionError e = thrown()
+        e.message.startsWith("Some builds have failed.")
+    }
+
+    def "fails when an operation fails"() {
+        given:
+        result.current.add(operation())
+        result.baselineVersions << baseline('oldVersion')
+        result.baselineVersions[0].results << operation()
+        result.baselineVersions[1].results << operation(failure: new RuntimeException())
+
+        when:
+        result.assertCurrentVersionHasNotRegressed()
+
+        then:
+        AssertionError e = thrown()
+        e.message.startsWith("Some builds have failed.")
+    }
+
+    def "fails if one of the baseline version is faster and the other needs less memory"() {
+        given:
+        result.displayName = '<test>'
+        result.baselineVersions[0].version = '1.1' //fast
+        result.baselineVersions[0].results << operation(heapUsed: 1200, executionTime: 100)
+        result.baselineVersions[0].results << operation(heapUsed: 1200, executionTime: 100)
+
+        result.baselineVersions << baseline("1.2") //needs less memory
+        result.baselineVersions[1].results << operation(heapUsed: 1000, executionTime: 150)
+        result.baselineVersions[1].results << operation(heapUsed: 1000, executionTime: 150)
+
+        and:
+        result.current << operation(heapUsed: 1100, executionTime: 125)
+        result.current << operation(heapUsed: 1100, executionTime: 125)
+
+        when:
+        result.assertCurrentVersionHasNotRegressed()
+
+        then:
+        AssertionError e = thrown()
+        e.message.contains("Speed <test>: we're slower than 1.1.")
+        e.message.contains('Difference: 25 ms slower')
+        e.message.contains('Memory <test>: we need more memory than 1.2.')
+        e.message.contains('Difference: 100 B more')
+        e.message.count('<test>') == 2
+    }
+
+    def "fails if all of the baseline versions are better in every respect"() {
+        given:
+        result.displayName = '<test>'
+        result.baselineVersions[0].version = '1.1' //fast
+        result.baselineVersions[0].results << operation(heapUsed: 1200, executionTime: 120)
+        result.baselineVersions[0].results << operation(heapUsed: 1200, executionTime: 120)
+
+        result.baselineVersions << baseline("1.2") //needs less memory
+        result.baselineVersions[1].results << operation(heapUsed: 1100, executionTime: 150)
+        result.baselineVersions[1].results << operation(heapUsed: 1100, executionTime: 150)
+
+        and:
+        result.current << operation(heapUsed: 1300, executionTime: 200)
+        result.current << operation(heapUsed: 1300, executionTime: 200)
+
+        when:
+        result.assertCurrentVersionHasNotRegressed()
+
+        then:
+        AssertionError e = thrown()
+        e.message.contains("Speed <test>: we're slower than 1.1.")
+        e.message.contains("Speed <test>: we're slower than 1.2.")
+        e.message.contains('Memory <test>: we need more memory than 1.1.')
+        e.message.contains('Memory <test>: we need more memory than 1.2.')
+    }
+
+    private MeasuredOperation operation(Map<String, Object> args) {
+        def operation = new MeasuredOperation()
+        operation.executionTime = Duration.millis(args?.executionTime ?: 120)
+        operation.totalMemoryUsed = DataAmount.bytes(args?.heapUsed ?: 1024)
+        operation.exception = args?.failure
+        return operation
+    }
+}
diff --git a/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/PrettyCalculatorSpec.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/PrettyCalculatorSpec.groovy
new file mode 100644
index 0000000..a7b3b23
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/PrettyCalculatorSpec.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture
+
+import spock.lang.Specification
+
+import static org.gradle.performance.fixture.PrettyCalculator.percentChange
+
+/**
+ * by Szczepan Faber, created at: 10/30/12
+ */
+class PrettyCalculatorSpec extends Specification {
+
+    def "knows percentage change"() {
+        expect:
+        percentChange(Amount.valueOf(current, Duration.SECONDS), Amount.valueOf(prevous, Duration.SECONDS)) == percent
+
+        where:
+        current  | prevous | percent
+        1        | 1       | 0
+        3        | 1       | 200
+        1        | 3       | -66.67
+        2        | 3       | -33.33
+        5        | 4       | 25
+        2.2      | 3.567   | -38.32
+        12.00001 | 10.23   | 17.30
+        //not strictly true, but for our purposes should do:
+        0        | 3       | -100
+        300      | 0       | 100
+    }
+}
\ No newline at end of file
diff --git a/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/UnitsTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/UnitsTest.groovy
new file mode 100644
index 0000000..ccd0b47
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/performance/fixture/UnitsTest.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.performance.fixture
+
+import spock.lang.Specification
+
+class UnitsTest extends Specification {
+    def "can compare units of same quantity"() {
+        def base = Units.base(Void.class, "base")
+        def units1 = base.times(12, "units1")
+        def units2 = base.times(33, "units2")
+
+        expect:
+        base < units1
+        base < units2
+        units1 > base
+        units1 < units2
+        units2 > base
+        units2 > units1
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/Amount.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/Amount.java
deleted file mode 100644
index 8b348a2..0000000
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/Amount.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.peformance.fixture;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.text.DecimalFormat;
-import java.util.List;
-
-/**
- * An amount is an immutable value of some quantity, such as duration or length. Each amount has a decimal value and associated units.
- *
- * TODO - need to sort out scaling when dividing or converting between units.
- */
-public class Amount<Q> implements Comparable<Amount<Q>> {
-    private final BigDecimal value;
-    private final Units<Q> units;
-    private final BigDecimal normalised;
-
-    private Amount(BigDecimal value, Units<Q> units) {
-        this.value = value;
-        this.units = units;
-        normalised = units.scaleTo(value, units.getBaseUnits());
-    }
-
-    public static <Q> Amount<Q> valueOf(long value, Units<Q> units) {
-        return valueOf(BigDecimal.valueOf(value), units);
-    }
-
-    public static <Q> Amount<Q> valueOf(BigDecimal value, Units<Q> units) {
-        return new Amount<Q>(value, units);
-    }
-
-    /**
-     * Returns a string representation of this amount. Uses the original value and units of this amount.
-     */
-    @Override
-    public String toString() {
-        return String.format("%s %s", value, units.format(value));
-    }
-
-    public Units<Q> getUnits() {
-        return units;
-    }
-
-    public BigDecimal getValue() {
-        return value;
-    }
-
-    /**
-     * Returns a human consumable string representation of this amount.
-     */
-    public String format() {
-        List<Units<Q>> allUnits = units.getUnitsForQuantity();
-        BigDecimal base = normalised.abs();
-        for (int i = allUnits.size()-1; i >= 0; i--) {
-            Units<Q> candidate = allUnits.get(i);
-            if (base.compareTo(candidate.getFactor()) >= 0) {
-                BigDecimal scaled = units.scaleTo(value, candidate);
-                return String.format("%s %s", new DecimalFormat("#.###").format(scaled), candidate.format(scaled));
-            }
-        }
-        return String.format("%s %s", new DecimalFormat("#.###").format(value), units.format(value));
-    }
-
-    /**
-     * Converts this amount to an equivalent amount in the specified units.
-     *
-     * @return The converted amount.
-     */
-    public Amount<Q> toUnits(Units<Q> units) {
-        if (units.equals(this.units)) {
-            return this;
-        }
-        return new Amount<Q>(this.units.scaleTo(value, units), units);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj == null || obj.getClass() != getClass()) {
-            return false;
-        }
-        Amount<Q> other = (Amount<Q>) obj;
-        return compareTo(other) == 0;
-    }
-
-    @Override
-    public int hashCode() {
-        return normalised.hashCode();
-    }
-
-    public int compareTo(Amount<Q> o) {
-        if (o.units.getType() != units.getType()) {
-            throw new IllegalArgumentException(String.format("Cannot compare amount with units %s.", o.units));
-        }
-        return normalised.compareTo(o.normalised);
-    }
-
-    public Amount<Q> plus(Amount<Q> other) {
-        if (other.value.equals(BigDecimal.ZERO)) {
-            return this;
-        }
-        if (value.equals(BigDecimal.ZERO)) {
-            return other;
-        }
-        int diff = units.compareTo(other.units);
-        if (diff == 0) {
-            return new Amount<Q>(value.add(other.value), units);
-        }
-        if (diff < 0) {
-            return new Amount<Q>(value.add(other.units.scaleTo(other.value, units)), units);
-        }
-        return new Amount<Q>(units.scaleTo(value, other.units).add(other.value), other.units);
-    }
-
-    public Amount<Q> minus(Amount<Q> other) {
-        if (other.value.equals(BigDecimal.ZERO)) {
-            return this;
-        }
-        int diff = units.compareTo(other.units);
-        if (diff == 0) {
-            return new Amount<Q>(value.subtract(other.value), units);
-        }
-        if (diff < 0) {
-            return new Amount<Q>(value.subtract(other.units.scaleTo(other.value, units)), units);
-        }
-        return new Amount<Q>(units.scaleTo(value, other.units).subtract(other.value), other.units);
-    }
-
-    public Amount<Q> div(long other) {
-        return new Amount<Q>(value.divide(BigDecimal.valueOf(other), 6, RoundingMode.HALF_UP), units);
-    }
-
-    public BigDecimal div(Amount<Q> other) {
-        return normalised.divide(other.normalised, 6, RoundingMode.HALF_UP);
-    }
-
-    public Amount<Q> abs() {
-        if (value.compareTo(BigDecimal.ZERO) >= 0) {
-            return this;
-        }
-        return new Amount<Q>(value.abs(), units);
-    }
-}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/DataAmount.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/DataAmount.java
deleted file mode 100644
index 574e8d9..0000000
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/DataAmount.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.peformance.fixture;
-
-import java.math.BigDecimal;
-
-public class DataAmount {
-    public static final Units<DataAmount> BYTES = Units.base(DataAmount.class, "B");
-    public static final Units<DataAmount> KILO_BYTES = BYTES.times(1024, "kB");
-    public static final Units<DataAmount> MEGA_BYTES = KILO_BYTES.times(1024, "MB");
-    public static final Units<DataAmount> GIGA_BYTES = MEGA_BYTES.times(1024, "GB");
-
-    public static Amount<DataAmount> bytes(long value) {
-        return Amount.valueOf(value, BYTES);
-    }
-
-    public static Amount<DataAmount> bytes(BigDecimal value) {
-        return Amount.valueOf(value, BYTES);
-    }
-
-    public static Amount<DataAmount> kbytes(BigDecimal value) {
-        return Amount.valueOf(value, KILO_BYTES);
-    }
-}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/DataCollector.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/DataCollector.java
deleted file mode 100644
index 7eeee84..0000000
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/DataCollector.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.peformance.fixture;
-
-import java.io.File;
-
-/**
- * by Szczepan Faber, created at: 8/14/12
- */
-public interface DataCollector {
-
-    void collect(File testProjectDir, MeasuredOperation operation);
-}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/Duration.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/Duration.java
deleted file mode 100644
index 2422d94..0000000
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/Duration.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.peformance.fixture;
-
-import java.math.BigDecimal;
-
-public class Duration {
-    public static final Units<Duration> MILLI_SECONDS = Units.base(Duration.class, "ms");
-    public static final Units<Duration> SECONDS = MILLI_SECONDS.times(1000, "s");
-    public static final Units<Duration> MINUTES = SECONDS.times(60, "m");
-    public static final Units<Duration> HOURS = MINUTES.times(60, "h");
-
-    public static Amount<Duration> millis(long millis) {
-        return Amount.valueOf(millis, MILLI_SECONDS);
-    }
-
-    public static Amount<Duration> millis(BigDecimal millis) {
-        return Amount.valueOf(millis, MILLI_SECONDS);
-    }
-
-    public static Amount<Duration> seconds(BigDecimal seconds) {
-        return Amount.valueOf(seconds, SECONDS);
-    }
-
-    public static Amount<Duration> minutes(BigDecimal minutes) {
-        return Amount.valueOf(minutes, MINUTES);
-    }
-
-    public static Amount<Duration> hours(BigDecimal hours) {
-        return Amount.valueOf(hours, HOURS);
-    }
-}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/MeasuredOperation.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/MeasuredOperation.groovy
deleted file mode 100644
index 6c1c32e..0000000
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/MeasuredOperation.groovy
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.peformance.fixture
-
-import org.gradle.util.Clock
-
-/**
- * by Szczepan Faber, created at: 2/10/12
- */
-public class MeasuredOperation {
-    Amount<Duration> executionTime
-    Exception exception
-    Amount<DataAmount> totalMemoryUsed
-
-    static MeasuredOperation measure(Closure operation) {
-        def out = new MeasuredOperation()
-        def clock = new Clock()
-        clock.reset()
-        try {
-            operation(out)
-        } catch (Exception e) {
-            out.exception = e
-        }
-        out.executionTime = Duration.millis(clock.timeInMs)
-        return out
-    }
-}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/MeasuredOperationList.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/MeasuredOperationList.groovy
deleted file mode 100644
index 04f3c52..0000000
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/MeasuredOperationList.groovy
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.peformance.fixture
-
-/**
- * by Szczepan Faber, created at: 2/10/12
- */
-public class MeasuredOperationList extends LinkedList<MeasuredOperation> {
-    String name
-
-    Amount<DataAmount> avgMemory() {
-        def bytes = this.collect { it.totalMemoryUsed }
-        bytes.sum() / bytes.size()
-    }
-
-    Amount<DataAmount> minMemory() {
-        return collect { it.totalMemoryUsed }.min()
-    }
-
-    Amount<DataAmount> maxMemory() {
-        return collect { it.totalMemoryUsed }.max()
-    }
-
-    Amount<Duration> avgTime() {
-        def currentTimes = this.collect { it.executionTime }
-        currentTimes.sum() / currentTimes.size()
-    }
-
-    Amount<Duration> maxTime() {
-        return collect { it.executionTime }.max()
-    }
-
-    Amount<Duration> minTime() {
-        return collect { it.executionTime }.min()
-    }
-}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/MemoryInfoCollector.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/MemoryInfoCollector.groovy
deleted file mode 100644
index 475292c..0000000
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/MemoryInfoCollector.groovy
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.peformance.fixture
-
-/**
- * by Szczepan Faber, created at: 8/14/12
- */
-public class MemoryInfoCollector implements DataCollector {
-
-    String outputFileName
-
-    public void collect(File testProjectDir, MeasuredOperation operation) {
-        def file = new File(testProjectDir, outputFileName)
-        if (!file.exists()) {
-            throw new RuntimeException("Cannot find $file. Cannot collect memory information if the build hasn't produced one.")
-        }
-        long bytesConsumed = Long.parseLong(file.text)
-        operation.totalMemoryUsed = DataAmount.bytes(bytesConsumed)
-    }
-}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/PerformanceResults.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/PerformanceResults.groovy
deleted file mode 100644
index 872f078..0000000
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/PerformanceResults.groovy
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.peformance.fixture
-
-import org.gradle.api.logging.Logging
-
-import static PrettyCalculator.prettyBytes
-import static PrettyCalculator.prettyTime
-import static org.gradle.peformance.fixture.PrettyCalculator.toBytes
-import static org.gradle.peformance.fixture.PrettyCalculator.toMillis
-
-public class PerformanceResults {
-
-    String displayName
-    Amount<Duration> maxExecutionTimeRegression = Duration.millis(0)
-    Amount<DataAmount> maxMemoryRegression = DataAmount.bytes(0)
-
-    private final static LOGGER = Logging.getLogger(PerformanceTestRunner.class)
-
-    final MeasuredOperationList previous = new MeasuredOperationList(name: "Previous release")
-    final MeasuredOperationList current = new MeasuredOperationList(name:  "Current Gradle")
-    final Map<String, MeasuredOperationList> others = new TreeMap<>()
-
-    def clear() {
-        previous.clear();
-        current.clear();
-        others.values()*.clear()
-    }
-
-    void assertEveryBuildSucceeds() {
-        LOGGER.info("Asserting all builds have succeeded...");
-        assert previous.size() == current.size()
-        def failures = []
-        failures.addAll previous.findAll { it.exception }
-        others.values().each {
-            failures.addAll it.findAll { it.exception }
-        }
-        failures.addAll current.findAll { it.exception }
-        assert failures.collect { it.exception }.empty : "Some builds have failed."
-    }
-
-    void assertCurrentVersionHasNotRegressed() {
-        def slower = assertCurrentReleaseIsNotSlower()
-        def larger = assertMemoryUsed()
-        if (slower && larger) {
-            throw new AssertionError("$slower\n$larger")
-        }
-        if (slower) {
-            throw new AssertionError(slower)
-        }
-        if (larger) {
-            throw new AssertionError(larger)
-        }
-        assertEveryBuildSucceeds()
-    }
-
-    private String assertMemoryUsed() {
-        def failed = (current.avgMemory() - previous.avgMemory()) > maxMemoryRegression
-
-        String message;
-        if (current.avgMemory() > previous.avgMemory()) {
-            message = "Memory $displayName: current Gradle needs a little more memory on average."
-        } else {
-            message = "Memory $displayName: AWESOME! current Gradle needs less memory on average :D"
-        }
-        message += "\n${memoryStats()}"
-        println("\n$message")
-        return failed ? message : null
-    }
-
-    private String assertCurrentReleaseIsNotSlower() {
-        def failed = (current.avgTime() - previous.avgTime()) > maxExecutionTimeRegression
-
-        String message;
-        if (current.avgTime() > previous.avgTime()) {
-            message = "Speed $displayName: current Gradle is a little slower on average."
-        } else {
-            message = "Speed $displayName: AWESOME! current Gradle is faster on average :D"
-        }
-        message += "\n${speedStats()}"
-        println("\n$message")
-        return failed ? message : null
-    }
-
-    String memoryStats() {
-        def result = new StringBuilder()
-        result.append(memoryStats(previous))
-        others.values().each {
-            result.append(memoryStats(it))
-        }
-        result.append(memoryStats(current))
-        def diff = current.avgMemory() - previous.avgMemory()
-        def desc = diff > DataAmount.bytes(0) ? "more" : "less"
-        result.append("Difference: ${prettyBytes(diff.abs())} $desc (${toBytes(diff.abs())}), ${PrettyCalculator.percentChange(current.avgMemory(), previous.avgMemory())}%, max regression: ${prettyBytes(maxMemoryRegression)}")
-        return result.toString()
-    }
-
-    String memoryStats(MeasuredOperationList list) {
-        """  ${list.name} avg: ${prettyBytes(list.avgMemory())} ${list.collect { prettyBytes(it.totalMemoryUsed) }}
-  ${list.name} min: ${prettyBytes(list.minMemory())}, max: ${prettyBytes(list.maxMemory())}
-"""
-    }
-
-    String speedStats() {
-        def result = new StringBuilder()
-        result.append(speedStats(previous))
-        others.values().each {
-            result.append(speedStats(it))
-        }
-        result.append(speedStats(current))
-        def diff = current.avgTime() - previous.avgTime()
-        def desc = diff > Duration.millis(0) ? "slower" : "faster"
-        result.append("Difference: ${prettyTime(diff.abs())} $desc (${toMillis(diff.abs())}), ${PrettyCalculator.percentChange(current.avgTime(), previous.avgTime())}%, max regression: ${prettyTime(maxExecutionTimeRegression)}")
-        return result.toString()
-    }
-
-    String speedStats(MeasuredOperationList list) {
-        """  ${list.name} avg: ${prettyTime(list.avgTime())} ${list.collect { prettyTime(it.executionTime) }}
-  ${list.name} min: ${prettyTime(list.minTime())}, max: ${prettyTime(list.maxTime())}
-"""
-    }
-}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/PerformanceTestRunner.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/PerformanceTestRunner.groovy
deleted file mode 100644
index 7dc458e..0000000
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/PerformanceTestRunner.groovy
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.peformance.fixture
-
-import org.gradle.api.logging.Logging
-import org.gradle.integtests.fixtures.*
-
-public class PerformanceTestRunner {
-
-    private final static LOGGER = Logging.getLogger(PerformanceTestRunner.class)
-
-    def current = new GradleDistribution()
-    def previous = new ReleasedVersions(current).last
-
-    String testProject
-    int runs
-    int warmUpRuns
-    Amount<Duration> maxExecutionTimeRegression
-    Amount<DataAmount> maxMemoryRegression
-    List<String> otherVersions = []
-    List<String> tasksToRun = []
-    DataCollector dataCollector = new MemoryInfoCollector(outputFileName: "build/totalMemoryUsed.txt")
-    List<String> args = []
-
-    PerformanceResults results
-
-    PerformanceResults run() {
-        results = new PerformanceResults(maxExecutionTimeRegression: maxExecutionTimeRegression, maxMemoryRegression: maxMemoryRegression, displayName: "Results for test project '$testProject' with tasks ${tasksToRun.join(', ')}")
-        results.previous.name = "Gradle ${previous.version}"
-        otherVersions.each { results.others[it] = new MeasuredOperationList(name: "Gradle $it") }
-
-        println "Running performance tests for test project '$testProject', no. of runs: $runs"
-        warmUpRuns.times {
-            println "Executing warm-up run #${it + 1}"
-            runOnce()
-        }
-        results.clear()
-        runs.times {
-            println "Executing test run #${it + 1}"
-            runOnce()
-        }
-        results
-    }
-
-    void runOnce() {
-        File projectDir = new TestProjectLocator().findProjectDir(testProject)
-        runOnce(previous, projectDir, results.previous)
-        otherVersions.each {
-            runOnce(current.previousVersion(it), projectDir, results.others[it])
-        }
-        runOnce(current, projectDir, results.current)
-    }
-
-    void runOnce(BasicGradleDistribution dist, File projectDir, MeasuredOperationList results) {
-        def executer = this.executer(dist, projectDir)
-        def operation = MeasuredOperation.measure { MeasuredOperation operation ->
-            executer.run()
-        }
-        dataCollector.collect(projectDir, operation)
-        results.add(operation)
-    }
-
-    GradleExecuter executer(BasicGradleDistribution dist, File projectDir) {
-        def executer
-        if (dist instanceof GradleDistribution) {
-            executer = new GradleDistributionExecuter(GradleDistributionExecuter.Executer.forking, dist)
-            executer.withDeprecationChecksDisabled()
-            executer.withStackTraceChecksDisabled()
-        } else {
-            executer = dist.executer()
-        }
-        executer.withGradleUserHomeDir(current.userHomeDir)
-        return executer.withArguments('-u').inDirectory(projectDir).withTasks(tasksToRun).withArguments(args)
-    }
-}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/PrettyCalculator.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/PrettyCalculator.groovy
deleted file mode 100644
index 5538326..0000000
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/PrettyCalculator.groovy
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.peformance.fixture
-
-import java.math.RoundingMode
-
-/**
- * by Szczepan Faber, created at: 10/30/12
- */
-class PrettyCalculator {
-
-    static String prettyBytes(Amount<DataAmount> bytes) {
-        return bytes.format()
-    }
-
-    static String toBytes(Amount<DataAmount> bytes) {
-        return bytes.toUnits(DataAmount.BYTES).value.setScale(3, RoundingMode.HALF_UP).stripTrailingZeros().toString() + " B"
-    }
-
-    static String prettyTime(Amount<Duration> duration) {
-        return duration.format()
-    }
-
-    static String toMillis(Amount<Duration> duration) {
-        return duration.toUnits(Duration.MILLI_SECONDS).value.setScale(3, RoundingMode.HALF_UP).stripTrailingZeros().toString() + " ms"
-    }
-
-    static <Q> Number percentChange(Amount<Q> current, Amount<Q> previous) {
-        if (previous == Amount.valueOf(0, previous.getUnits())) {
-            return 100
-        }
-        BigDecimal result = (current - previous) / previous * 100
-        return result.setScale(2, RoundingMode.HALF_UP)
-    }
-}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/TestProjectLocator.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/TestProjectLocator.groovy
deleted file mode 100644
index 0f3d530..0000000
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/TestProjectLocator.groovy
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.peformance.fixture
-
-/**
- * by Szczepan Faber, created at: 2/10/12
- */
-class TestProjectLocator {
-
-    File findProjectDir(String name) {
-        def base = "subprojects/performance/build"
-        def locations = ["$base/$name", "../../$base/$name"]
-        def dirs = locations.collect { new File(it).absoluteFile }
-        for (File dir: dirs) {
-            if (dir.isDirectory()) {
-                return dir
-            }
-        }
-        def message = "Looks like the test project '$name' was not generated.\nI've tried to find it at:\n"
-        dirs.each { message += "  $it\n" }
-        message +="Please run 'gradlew performance:$name' to generate the test project."
-        throw new IllegalArgumentException(message)
-    }
-}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/Units.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/Units.java
deleted file mode 100644
index 1fe5f8a..0000000
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/Units.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.peformance.fixture;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-public abstract class Units<Q> implements Comparable<Units<Q>> {
-    private final Class<Q> type;
-    private final String displaySingular;
-    private final String displayPlural;
-
-    protected Units(Class<Q> type, String displaySingular, String displayPlural) {
-        this.type = type;
-        this.displaySingular = displaySingular;
-        this.displayPlural = displayPlural;
-    }
-
-    /**
-     * Creates the base units for a given quantity.
-     */
-    public static <Q> Units<Q> base(Class<Q> type, String displaySingular) {
-        return new BaseUnits<Q>(type, displaySingular, displaySingular);
-    }
-
-    /**
-     * Creates the base units for a given quantity.
-     */
-    public static <Q> Units<Q> base(Class<Q> type, String displaySingular, String displayPlural) {
-        return new BaseUnits<Q>(type, displaySingular, displayPlural);
-    }
-
-    protected Class<Q> getType() {
-        return type;
-    }
-
-    @Override
-    public String toString() {
-        return displayPlural;
-    }
-
-    /**
-     * Creates units that are some multiple of this units.
-     */
-    public abstract Units<Q> times(long value, String displaySingular, String displayPlural);
-
-    /**
-     * Creates units that are some multiple of this units.
-     */
-    public Units<Q> times(long value, String displaySingular) {
-        return times(value, displaySingular, displaySingular);
-    }
-
-    protected abstract Units<Q> getBaseUnits();
-
-    protected abstract List<Units<Q>> getUnitsForQuantity();
-
-    protected abstract BigDecimal getFactor();
-
-    protected abstract BigDecimal scaleTo(BigDecimal value, Units<Q> units);
-
-    protected String format(BigDecimal value) {
-        return value.compareTo(BigDecimal.ONE) == 0 ? displaySingular : displayPlural;
-    }
-
-    private static class BaseUnits<Q> extends Units<Q> {
-        private final List<Units<Q>> units = new ArrayList<Units<Q>>();
-
-        protected BaseUnits(Class<Q> type, String displaySingular, String displayPlural) {
-            super(type, displaySingular, displayPlural);
-            units.add(this);
-        }
-
-        @Override
-        public BigDecimal scaleTo(BigDecimal value, Units<Q> units) {
-            if (units == this) {
-                return value;
-            }
-            ScaledUnits<Q> scaledUnits = (ScaledUnits<Q>) units;
-            return value.divide(scaledUnits.factor, 6, RoundingMode.HALF_UP).stripTrailingZeros();
-        }
-
-        @Override
-        protected List<Units<Q>> getUnitsForQuantity() {
-            return units;
-        }
-
-        @Override
-        public Units<Q> times(long value, String displaySingular, String displayPlural) {
-            return new ScaledUnits<Q>(this, displaySingular, displayPlural, BigDecimal.valueOf(value));
-        }
-
-        @Override
-        protected Units<Q> getBaseUnits() {
-            return this;
-        }
-
-        @Override
-        protected BigDecimal getFactor() {
-            return BigDecimal.ONE;
-        }
-
-        public int compareTo(Units<Q> o) {
-            if (o == this) {
-                return 0;
-            }
-            if (o.getType() != getType()) {
-                throw new IllegalArgumentException(String.format("Cannot compare units of %s with units of %s.", getType(), o.getType()));
-            }
-            return -1;
-        }
-
-        public void add(ScaledUnits<Q> units) {
-            this.units.add(units);
-            Collections.sort(this.units);
-        }
-    }
-
-    private static class ScaledUnits<Q> extends Units<Q> {
-        private final BaseUnits<Q> baseUnits;
-        private final BigDecimal factor;
-
-        public ScaledUnits(BaseUnits<Q> baseUnits, String displaySingular, String displayPlural, BigDecimal factor) {
-            super(baseUnits.getType(), displaySingular, displayPlural);
-            assert factor.compareTo(BigDecimal.ONE) > 0;
-            this.baseUnits = baseUnits;
-            this.factor = factor;
-            baseUnits.add(this);
-        }
-
-        @Override
-        public Units<Q> times(long value, String displaySingular, String displayPlural) {
-            return new ScaledUnits<Q>(baseUnits, displaySingular, displayPlural, factor.multiply(BigDecimal.valueOf(value)));
-        }
-
-        @Override
-        public BigDecimal scaleTo(BigDecimal value, Units<Q> units) {
-            if (units == this) {
-                return value;
-            }
-            if (units.equals(baseUnits)) {
-                return value.multiply(factor);
-            }
-            ScaledUnits<Q> other = (ScaledUnits<Q>) units;
-            return value.multiply(factor).divide(other.factor, 6, RoundingMode.HALF_UP).stripTrailingZeros();
-        }
-
-        @Override
-        protected List<Units<Q>> getUnitsForQuantity() {
-            return baseUnits.getUnitsForQuantity();
-        }
-
-        @Override
-        protected Units<Q> getBaseUnits() {
-            return baseUnits;
-        }
-
-        @Override
-        protected BigDecimal getFactor() {
-            return factor;
-        }
-
-        public int compareTo(Units<Q> o) {
-            if (o.getType() != getType()) {
-                throw new IllegalArgumentException(String.format("Cannot compare units of %s with units of %s.", getType(), o.getType()));
-            }
-            if (o.equals(baseUnits)) {
-                return 1;
-            }
-            ScaledUnits<Q> other = (ScaledUnits<Q>) o;
-            if (!other.baseUnits.equals(baseUnits)) {
-                throw new IllegalArgumentException("Cannot compare units with different base units.");
-            }
-            return factor.compareTo(other.factor);
-        }
-    }
-}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Amount.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Amount.java
new file mode 100644
index 0000000..7984ff8
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Amount.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.performance.fixture;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * An amount is an immutable value of some quantity, such as duration or length. Each amount has a decimal value and associated units.
+ *
+ * TODO - need to sort out scaling when dividing or converting between units.
+ */
+public class Amount<Q> implements Comparable<Amount<Q>> {
+    private final BigDecimal value;
+    private final Units<Q> units;
+    private final BigDecimal normalised;
+
+    private Amount(BigDecimal value, Units<Q> units) {
+        this.value = value;
+        this.units = units;
+        normalised = units.scaleTo(value, units.getBaseUnits());
+    }
+
+    public static <Q> Amount<Q> valueOf(long value, Units<Q> units) {
+        return valueOf(BigDecimal.valueOf(value), units);
+    }
+
+    public static <Q> Amount<Q> valueOf(BigDecimal value, Units<Q> units) {
+        return new Amount<Q>(value, units);
+    }
+
+    /**
+     * Returns a string representation of this amount. Uses the original value and units of this amount.
+     */
+    @Override
+    public String toString() {
+        return String.format("%s %s", value, units.format(value));
+    }
+
+    public Units<Q> getUnits() {
+        return units;
+    }
+
+    public BigDecimal getValue() {
+        return value;
+    }
+
+    /**
+     * Returns a human consumable string representation of this amount.
+     */
+    public String format() {
+        List<Units<Q>> allUnits = units.getUnitsForQuantity();
+        BigDecimal base = normalised.abs();
+        for (int i = allUnits.size()-1; i >= 0; i--) {
+            Units<Q> candidate = allUnits.get(i);
+            if (base.compareTo(candidate.getFactor()) >= 0) {
+                BigDecimal scaled = units.scaleTo(value, candidate);
+                return String.format("%s %s", new DecimalFormat("#.###", new DecimalFormatSymbols(Locale.US)).format(scaled), candidate.format(scaled));
+            }
+        }
+        return String.format("%s %s", new DecimalFormat("#.###", new DecimalFormatSymbols(Locale.US)).format(value), units.format(value));
+    }
+
+    /**
+     * Converts this amount to an equivalent amount in the specified units.
+     *
+     * @return The converted amount.
+     */
+    public Amount<Q> toUnits(Units<Q> units) {
+        if (units.equals(this.units)) {
+            return this;
+        }
+        return new Amount<Q>(this.units.scaleTo(value, units), units);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+        Amount<Q> other = (Amount<Q>) obj;
+        return compareTo(other) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return normalised.hashCode();
+    }
+
+    public int compareTo(Amount<Q> o) {
+        if (o.units.getType() != units.getType()) {
+            throw new IllegalArgumentException(String.format("Cannot compare amount with units %s.", o.units));
+        }
+        return normalised.compareTo(o.normalised);
+    }
+
+    public Amount<Q> plus(Amount<Q> other) {
+        if (other.value.equals(BigDecimal.ZERO)) {
+            return this;
+        }
+        if (value.equals(BigDecimal.ZERO)) {
+            return other;
+        }
+        int diff = units.compareTo(other.units);
+        if (diff == 0) {
+            return new Amount<Q>(value.add(other.value), units);
+        }
+        if (diff < 0) {
+            return new Amount<Q>(value.add(other.units.scaleTo(other.value, units)), units);
+        }
+        return new Amount<Q>(units.scaleTo(value, other.units).add(other.value), other.units);
+    }
+
+    public Amount<Q> minus(Amount<Q> other) {
+        if (other.value.equals(BigDecimal.ZERO)) {
+            return this;
+        }
+        int diff = units.compareTo(other.units);
+        if (diff == 0) {
+            return new Amount<Q>(value.subtract(other.value), units);
+        }
+        if (diff < 0) {
+            return new Amount<Q>(value.subtract(other.units.scaleTo(other.value, units)), units);
+        }
+        return new Amount<Q>(units.scaleTo(value, other.units).subtract(other.value), other.units);
+    }
+
+    public Amount<Q> div(long other) {
+        return new Amount<Q>(value.divide(BigDecimal.valueOf(other), 6, RoundingMode.HALF_UP), units);
+    }
+
+    public BigDecimal div(Amount<Q> other) {
+        return normalised.divide(other.normalised, 6, RoundingMode.HALF_UP);
+    }
+
+    public Amount<Q> abs() {
+        if (value.compareTo(BigDecimal.ZERO) >= 0) {
+            return this;
+        }
+        return new Amount<Q>(value.abs(), units);
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/BaselineVersion.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/BaselineVersion.groovy
new file mode 100644
index 0000000..93a7bb5
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/BaselineVersion.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture
+
+import static org.gradle.performance.fixture.PrettyCalculator.*
+
+/**
+ * by Szczepan Faber, created at: 11/20/12
+ */
+class BaselineVersion {
+
+    String version
+    Amount<Duration> maxExecutionTimeRegression = Duration.millis(0)
+    Amount<DataAmount> maxMemoryRegression = DataAmount.bytes(0)
+
+    MeasuredOperationList results
+
+    void clearResults() {
+        results.clear()
+    }
+
+    static BaselineVersion baseline(String version) {
+        new BaselineVersion(version: version, results: new MeasuredOperationList(name: "Gradle $version"))
+    }
+
+    String getSpeedStatsAgainst(String displayName, MeasuredOperationList current) {
+        def sb = new StringBuilder()
+        if (current.avgTime() > results.avgTime()) {
+            sb.append "Speed $displayName: we're slower than $version.\n"
+        } else {
+            sb.append "Speed $displayName: AWESOME! we're faster than $version :D\n"
+        }
+        def diff = current.avgTime() - results.avgTime()
+        def desc = diff > Duration.millis(0) ? "slower" : "faster"
+        sb.append("Difference: ${prettyTime(diff.abs())} $desc (${toMillis(diff.abs())}), ${PrettyCalculator.percentChange(current.avgTime(), results.avgTime())}%, max regression: ${prettyTime(maxExecutionTimeRegression)}\n")
+        sb.append(current.speedStats)
+        sb.append(results.speedStats)
+        sb.append("\n")
+        sb.toString()
+    }
+
+    String getMemoryStatsAgainst(String displayName, MeasuredOperationList current) {
+        def sb = new StringBuilder()
+        if (current.avgMemory() > results.avgMemory()) {
+            sb.append("Memory $displayName: we need more memory than $version.\n")
+        } else {
+            sb.append("Memory $displayName: AWESOME! we need less memory than $version :D\n")
+        }
+        def diff = current.avgMemory() - results.avgMemory()
+        def desc = diff > DataAmount.bytes(0) ? "more" : "less"
+        sb.append("Difference: ${prettyBytes(diff.abs())} $desc (${toBytes(diff.abs())}), ${PrettyCalculator.percentChange(current.avgMemory(), results.avgMemory())}%, max regression: ${prettyBytes(maxMemoryRegression)}\n")
+        sb.append(current.memoryStats)
+        sb.append(results.memoryStats)
+        sb.append("\n")
+        sb.toString()
+    }
+
+    boolean usesLessMemoryThan(MeasuredOperationList current) {
+        current.avgMemory() - results.avgMemory() > maxMemoryRegression
+    }
+
+    boolean fasterThan(MeasuredOperationList current) {
+        current.avgTime() - results.avgTime() > maxExecutionTimeRegression
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/DataAmount.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/DataAmount.java
new file mode 100644
index 0000000..7d0707d
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/DataAmount.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.performance.fixture;
+
+import java.math.BigDecimal;
+
+public class DataAmount {
+    public static final Units<DataAmount> BYTES = Units.base(DataAmount.class, "B");
+    public static final Units<DataAmount> KILO_BYTES = BYTES.times(1024, "kB");
+    public static final Units<DataAmount> MEGA_BYTES = KILO_BYTES.times(1024, "MB");
+    public static final Units<DataAmount> GIGA_BYTES = MEGA_BYTES.times(1024, "GB");
+
+    public static Amount<DataAmount> bytes(long value) {
+        return Amount.valueOf(value, BYTES);
+    }
+
+    public static Amount<DataAmount> bytes(BigDecimal value) {
+        return Amount.valueOf(value, BYTES);
+    }
+
+    public static Amount<DataAmount> kbytes(BigDecimal value) {
+        return Amount.valueOf(value, KILO_BYTES);
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/DataCollector.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/DataCollector.java
new file mode 100644
index 0000000..2b387d8
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/DataCollector.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture;
+
+import java.io.File;
+
+/**
+ * by Szczepan Faber, created at: 8/14/12
+ */
+public interface DataCollector {
+
+    void collect(File testProjectDir, MeasuredOperation operation);
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Duration.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Duration.java
new file mode 100644
index 0000000..90803c8
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Duration.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.performance.fixture;
+
+import java.math.BigDecimal;
+
+public class Duration {
+    public static final Units<Duration> MILLI_SECONDS = Units.base(Duration.class, "ms");
+    public static final Units<Duration> SECONDS = MILLI_SECONDS.times(1000, "s");
+    public static final Units<Duration> MINUTES = SECONDS.times(60, "m");
+    public static final Units<Duration> HOURS = MINUTES.times(60, "h");
+
+    public static Amount<Duration> millis(long millis) {
+        return Amount.valueOf(millis, MILLI_SECONDS);
+    }
+
+    public static Amount<Duration> millis(BigDecimal millis) {
+        return Amount.valueOf(millis, MILLI_SECONDS);
+    }
+
+    public static Amount<Duration> seconds(BigDecimal seconds) {
+        return Amount.valueOf(seconds, SECONDS);
+    }
+
+    public static Amount<Duration> minutes(BigDecimal minutes) {
+        return Amount.valueOf(minutes, MINUTES);
+    }
+
+    public static Amount<Duration> hours(BigDecimal hours) {
+        return Amount.valueOf(hours, HOURS);
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MeasuredOperation.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MeasuredOperation.groovy
new file mode 100644
index 0000000..f9da914
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MeasuredOperation.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture
+
+import org.gradle.util.Clock
+
+/**
+ * by Szczepan Faber, created at: 2/10/12
+ */
+public class MeasuredOperation {
+    Amount<Duration> executionTime
+    Exception exception
+    Amount<DataAmount> totalMemoryUsed
+
+    static MeasuredOperation measure(Closure operation) {
+        def out = new MeasuredOperation()
+        def clock = new Clock()
+        clock.reset()
+        try {
+            operation(out)
+        } catch (Exception e) {
+            out.exception = e
+        }
+        out.executionTime = Duration.millis(clock.timeInMs)
+        return out
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MeasuredOperationList.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MeasuredOperationList.groovy
new file mode 100644
index 0000000..f49079b
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MeasuredOperationList.groovy
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture
+
+import static org.gradle.performance.fixture.PrettyCalculator.prettyBytes
+import static org.gradle.performance.fixture.PrettyCalculator.prettyTime
+
+/**
+ * by Szczepan Faber, created at: 2/10/12
+ */
+public class MeasuredOperationList extends LinkedList<MeasuredOperation> {
+    String name
+
+    Amount<DataAmount> avgMemory() {
+        def bytes = this.collect { it.totalMemoryUsed }
+        bytes.sum() / bytes.size()
+    }
+
+    Amount<DataAmount> minMemory() {
+        return collect { it.totalMemoryUsed }.min()
+    }
+
+    Amount<DataAmount> maxMemory() {
+        return collect { it.totalMemoryUsed }.max()
+    }
+
+    Amount<Duration> avgTime() {
+        def currentTimes = this.collect { it.executionTime }
+        currentTimes.sum() / currentTimes.size()
+    }
+
+    Amount<Duration> maxTime() {
+        return collect { it.executionTime }.max()
+    }
+
+    Amount<Duration> minTime() {
+        return collect { it.executionTime }.min()
+    }
+
+    String getSpeedStats() {
+        """  ${name} avg: ${prettyTime(avgTime())} ${collect { prettyTime(it.executionTime) }}
+  > min: ${prettyTime(minTime())}, max: ${prettyTime(maxTime())}
+"""
+    }
+
+    String getMemoryStats() {
+        """  ${name} avg: ${prettyBytes(avgMemory())} ${collect { prettyBytes(it.totalMemoryUsed) }}
+  > min: ${prettyBytes(minMemory())}, max: ${prettyBytes(maxMemory())}
+"""
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MemoryInfoCollector.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MemoryInfoCollector.groovy
new file mode 100644
index 0000000..107794b
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/MemoryInfoCollector.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture
+
+/**
+ * by Szczepan Faber, created at: 8/14/12
+ */
+public class MemoryInfoCollector implements DataCollector {
+
+    String outputFileName
+
+    public void collect(File testProjectDir, MeasuredOperation operation) {
+        def file = new File(testProjectDir, outputFileName).canonicalFile
+        if (!file.exists()) {
+            throw new RuntimeException("Cannot find $file. Cannot collect memory information if the build hasn't produced one.")
+        }
+        long bytesConsumed = Long.parseLong(file.text)
+        operation.totalMemoryUsed = DataAmount.bytes(bytesConsumed)
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PerformanceResults.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PerformanceResults.groovy
new file mode 100644
index 0000000..10e36fa
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PerformanceResults.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture
+
+import org.gradle.api.logging.Logging
+
+import static org.gradle.performance.fixture.BaselineVersion.baseline
+
+public class PerformanceResults {
+
+    private final static LOGGER = Logging.getLogger(PerformanceTestRunner.class)
+
+    List<BaselineVersion> baselineVersions = [ baseline("1.x")]
+    String displayName
+
+    final MeasuredOperationList current = new MeasuredOperationList(name:  "Current G.")
+
+    def clear() {
+        baselineVersions.each { it.clearResults() }
+        current.clear()
+    }
+
+    void assertEveryBuildSucceeds() {
+        LOGGER.info("Asserting all builds have succeeded...");
+        def failures = []
+        baselineVersions.each {
+            failures.addAll it.results.findAll { it.exception }
+        }
+        failures.addAll current.findAll { it.exception }
+
+        assert failures.collect { it.exception }.empty : "Some builds have failed."
+    }
+
+    void assertCurrentVersionHasNotRegressed() {
+        def slower = checkBaselineVersion({it.fasterThan(current)},         {it.getSpeedStatsAgainst(displayName, current)})
+        def larger = checkBaselineVersion({it.usesLessMemoryThan(current)}, {it.getMemoryStatsAgainst(displayName, current)})
+        assertEveryBuildSucceeds()
+        if (slower && larger) {
+            throw new AssertionError("$slower\n$larger")
+        }
+        if (slower) {
+            throw new AssertionError(slower)
+        }
+        if (larger) {
+            throw new AssertionError(larger)
+        }
+    }
+
+    private String checkBaselineVersion(Closure fails, Closure provideMessage) {
+        def failed = false
+        def failure = new StringBuilder()
+        baselineVersions.each {
+            String message = provideMessage(it)
+            if (fails(it)) {
+                failed = true
+                failure.append message
+            }
+            println message
+        }
+        return failed ? failure.toString() : null
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PerformanceTestRunner.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PerformanceTestRunner.groovy
new file mode 100644
index 0000000..2293c43
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PerformanceTestRunner.groovy
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture
+
+import org.gradle.api.logging.Logging
+import org.gradle.integtests.fixtures.executer.GradleDistribution
+import org.gradle.integtests.fixtures.executer.GradleExecuter
+import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext
+import org.gradle.integtests.fixtures.executer.UnderDevelopmentGradleDistribution
+import org.gradle.integtests.fixtures.versions.ReleasedVersionDistributions
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+
+public class PerformanceTestRunner {
+
+    private final static LOGGER = Logging.getLogger(PerformanceTestRunner.class)
+
+    def testDirectoryProvider = new TestNameTestDirectoryProvider()
+    def current = new UnderDevelopmentGradleDistribution()
+    def buildContext = new IntegrationTestBuildContext()
+
+    String testProject
+    int runs
+    int warmUpRuns
+
+    List<String> tasksToRun = []
+    DataCollector dataCollector = new MemoryInfoCollector(outputFileName: "build/totalMemoryUsed.txt")
+    List<String> args = []
+
+    List<String> targetVersions = ['last']
+    List<Amount<Duration>> maxExecutionTimeRegression = [Duration.millis(0)]
+    List<Amount<DataAmount>> maxMemoryRegression = [DataAmount.bytes(0)]
+
+    PerformanceResults results
+
+    PerformanceResults run() {
+        assert targetVersions.size() == maxExecutionTimeRegression.size()
+        assert targetVersions.size() == maxMemoryRegression.size()
+
+        def baselineVersions = []
+        targetVersions.eachWithIndex { it, idx ->
+            def mostRecentFinalRelease = new ReleasedVersionDistributions().mostRecentFinalRelease.version.version
+            def ver = (it == 'last') ? mostRecentFinalRelease : it
+            baselineVersions << new BaselineVersion(version: ver,
+                    maxExecutionTimeRegression: maxExecutionTimeRegression[idx],
+                    maxMemoryRegression: maxMemoryRegression[idx],
+                    results: new MeasuredOperationList(name: "Gradle $ver")
+            )
+        }
+
+        results = new PerformanceResults(
+                baselineVersions: baselineVersions,
+                displayName: "Results for test project '$testProject' with tasks ${tasksToRun.join(', ')}")
+
+        println "Running performance tests for test project '$testProject', no. of runs: $runs"
+        warmUpRuns.times {
+            println "Executing warm-up run #${it + 1}"
+            runOnce()
+        }
+        results.clear()
+        runs.times {
+            println "Executing test run #${it + 1}"
+            runOnce()
+        }
+        results
+    }
+
+    void runOnce() {
+        File projectDir = new TestProjectLocator().findProjectDir(testProject)
+        results.baselineVersions.reverse().each {
+            println "Gradle ${it.version}..."
+            runOnce(buildContext.distribution(it.version), projectDir, it.results)
+        }
+
+        println "Current Gradle..."
+        runOnce(current, projectDir, results.current)
+    }
+
+    void runOnce(GradleDistribution dist, File projectDir, MeasuredOperationList results) {
+        def executer = this.executer(dist, projectDir)
+        def operation = MeasuredOperation.measure { MeasuredOperation operation ->
+            executer.run()
+        }
+        dataCollector.collect(projectDir, operation)
+        results.add(operation)
+    }
+
+    GradleExecuter executer(GradleDistribution dist, File projectDir) {
+        dist.executer(testDirectoryProvider).
+                requireGradleHome(true).
+                withDeprecationChecksDisabled().
+                withStackTraceChecksDisabled().
+                withArguments('-u').
+                inDirectory(projectDir).
+                withTasks(tasksToRun).
+                withArguments(args)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PrettyCalculator.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PrettyCalculator.groovy
new file mode 100644
index 0000000..10d19ff
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PrettyCalculator.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture
+
+import java.math.RoundingMode
+
+/**
+ * by Szczepan Faber, created at: 10/30/12
+ */
+class PrettyCalculator {
+
+    static String prettyBytes(Amount<DataAmount> bytes) {
+        return bytes.format()
+    }
+
+    static String toBytes(Amount<DataAmount> bytes) {
+        return bytes.toUnits(DataAmount.BYTES).value.setScale(3, RoundingMode.HALF_UP).stripTrailingZeros().toString() + " B"
+    }
+
+    static String prettyTime(Amount<Duration> duration) {
+        return duration.format()
+    }
+
+    static String toMillis(Amount<Duration> duration) {
+        return duration.toUnits(Duration.MILLI_SECONDS).value.setScale(3, RoundingMode.HALF_UP).stripTrailingZeros().toString() + " ms"
+    }
+
+    static <Q> Number percentChange(Amount<Q> current, Amount<Q> previous) {
+        if (previous == Amount.valueOf(0, previous.getUnits())) {
+            return 100
+        }
+        BigDecimal result = (current - previous) / previous * 100
+        return result.setScale(2, RoundingMode.HALF_UP)
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/TestProjectLocator.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/TestProjectLocator.groovy
new file mode 100644
index 0000000..a109dca
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/TestProjectLocator.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture
+
+/**
+ * by Szczepan Faber, created at: 2/10/12
+ */
+class TestProjectLocator {
+
+    File findProjectDir(String name) {
+        def base = "subprojects/performance/build"
+        def locations = ["$base/$name", "../../$base/$name"]
+        def dirs = locations.collect { new File(it).absoluteFile }
+        for (File dir: dirs) {
+            if (dir.isDirectory()) {
+                return dir
+            }
+        }
+        def message = "Looks like the test project '$name' was not generated.\nI've tried to find it at:\n"
+        dirs.each { message += "  $it\n" }
+        message +="Please run 'gradlew performance:$name' to generate the test project."
+        throw new IllegalArgumentException(message)
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Units.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Units.java
new file mode 100644
index 0000000..0d787ad
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/Units.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.performance.fixture;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class Units<Q> implements Comparable<Units<Q>> {
+    private final Class<Q> type;
+    private final String displaySingular;
+    private final String displayPlural;
+
+    protected Units(Class<Q> type, String displaySingular, String displayPlural) {
+        this.type = type;
+        this.displaySingular = displaySingular;
+        this.displayPlural = displayPlural;
+    }
+
+    /**
+     * Creates the base units for a given quantity.
+     */
+    public static <Q> Units<Q> base(Class<Q> type, String displaySingular) {
+        return new BaseUnits<Q>(type, displaySingular, displaySingular);
+    }
+
+    /**
+     * Creates the base units for a given quantity.
+     */
+    public static <Q> Units<Q> base(Class<Q> type, String displaySingular, String displayPlural) {
+        return new BaseUnits<Q>(type, displaySingular, displayPlural);
+    }
+
+    protected Class<Q> getType() {
+        return type;
+    }
+
+    @Override
+    public String toString() {
+        return displayPlural;
+    }
+
+    /**
+     * Creates units that are some multiple of this units.
+     */
+    public abstract Units<Q> times(long value, String displaySingular, String displayPlural);
+
+    /**
+     * Creates units that are some multiple of this units.
+     */
+    public Units<Q> times(long value, String displaySingular) {
+        return times(value, displaySingular, displaySingular);
+    }
+
+    protected abstract Units<Q> getBaseUnits();
+
+    protected abstract List<Units<Q>> getUnitsForQuantity();
+
+    protected abstract BigDecimal getFactor();
+
+    protected abstract BigDecimal scaleTo(BigDecimal value, Units<Q> units);
+
+    protected String format(BigDecimal value) {
+        return value.compareTo(BigDecimal.ONE) == 0 ? displaySingular : displayPlural;
+    }
+
+    private static class BaseUnits<Q> extends Units<Q> {
+        private final List<Units<Q>> units = new ArrayList<Units<Q>>();
+
+        protected BaseUnits(Class<Q> type, String displaySingular, String displayPlural) {
+            super(type, displaySingular, displayPlural);
+            units.add(this);
+        }
+
+        @Override
+        public BigDecimal scaleTo(BigDecimal value, Units<Q> units) {
+            if (units == this) {
+                return value;
+            }
+            ScaledUnits<Q> scaledUnits = (ScaledUnits<Q>) units;
+            return value.divide(scaledUnits.factor, 6, RoundingMode.HALF_UP).stripTrailingZeros();
+        }
+
+        @Override
+        protected List<Units<Q>> getUnitsForQuantity() {
+            return units;
+        }
+
+        @Override
+        public Units<Q> times(long value, String displaySingular, String displayPlural) {
+            return new ScaledUnits<Q>(this, displaySingular, displayPlural, BigDecimal.valueOf(value));
+        }
+
+        @Override
+        protected Units<Q> getBaseUnits() {
+            return this;
+        }
+
+        @Override
+        protected BigDecimal getFactor() {
+            return BigDecimal.ONE;
+        }
+
+        public int compareTo(Units<Q> o) {
+            if (o == this) {
+                return 0;
+            }
+            if (o.getType() != getType()) {
+                throw new IllegalArgumentException(String.format("Cannot compare units of %s with units of %s.", getType(), o.getType()));
+            }
+            return -1;
+        }
+
+        public void add(ScaledUnits<Q> units) {
+            this.units.add(units);
+            Collections.sort(this.units);
+        }
+    }
+
+    private static class ScaledUnits<Q> extends Units<Q> {
+        private final BaseUnits<Q> baseUnits;
+        private final BigDecimal factor;
+
+        public ScaledUnits(BaseUnits<Q> baseUnits, String displaySingular, String displayPlural, BigDecimal factor) {
+            super(baseUnits.getType(), displaySingular, displayPlural);
+            assert factor.compareTo(BigDecimal.ONE) > 0;
+            this.baseUnits = baseUnits;
+            this.factor = factor;
+            baseUnits.add(this);
+        }
+
+        @Override
+        public Units<Q> times(long value, String displaySingular, String displayPlural) {
+            return new ScaledUnits<Q>(baseUnits, displaySingular, displayPlural, factor.multiply(BigDecimal.valueOf(value)));
+        }
+
+        @Override
+        public BigDecimal scaleTo(BigDecimal value, Units<Q> units) {
+            if (units == this) {
+                return value;
+            }
+            if (units.equals(baseUnits)) {
+                return value.multiply(factor);
+            }
+            ScaledUnits<Q> other = (ScaledUnits<Q>) units;
+            return value.multiply(factor).divide(other.factor, 6, RoundingMode.HALF_UP).stripTrailingZeros();
+        }
+
+        @Override
+        protected List<Units<Q>> getUnitsForQuantity() {
+            return baseUnits.getUnitsForQuantity();
+        }
+
+        @Override
+        protected Units<Q> getBaseUnits() {
+            return baseUnits;
+        }
+
+        @Override
+        protected BigDecimal getFactor() {
+            return factor;
+        }
+
+        public int compareTo(Units<Q> o) {
+            if (o.getType() != getType()) {
+                throw new IllegalArgumentException(String.format("Cannot compare units of %s with units of %s.", getType(), o.getType()));
+            }
+            if (o.equals(baseUnits)) {
+                return 1;
+            }
+            ScaledUnits<Q> other = (ScaledUnits<Q>) o;
+            if (!other.baseUnits.equals(baseUnits)) {
+                throw new IllegalArgumentException("Cannot compare units with different base units.");
+            }
+            return factor.compareTo(other.factor);
+        }
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy
index 6f497d4..b3016fe 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy
@@ -24,7 +24,7 @@ class BasePluginIntegrationTest extends AbstractIntegrationSpec {
     @Requires(TestPrecondition.MANDATORY_FILE_LOCKING)
     def "clean failure message indicates file"() {
         given:
-        executer.withForkingExecuter()
+        executer.requireGradleHome(true)
         buildFile << """
             apply plugin: 'base'
         """
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BuildSrcPluginTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BuildSrcPluginTest.groovy
index 486db0b..4df8b36 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BuildSrcPluginTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BuildSrcPluginTest.groovy
@@ -24,7 +24,7 @@ class BuildSrcPluginTest extends AbstractIntegrationSpec {
     @Issue("GRADLE-2001") // when using the daemon
     def "can use plugin from buildSrc that changes"() {
         given:
-        distribution.requireIsolatedDaemons() // make sure we get the same daemon both times
+        executer.requireIsolatedDaemons() // make sure we get the same daemon both times
 
         buildFile << "apply plugin: 'test-plugin'"
 
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/JavaLibraryDistributionIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/JavaLibraryDistributionIntegrationTest.groovy
new file mode 100755
index 0000000..847b7f2
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/JavaLibraryDistributionIntegrationTest.groovy
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.plugins
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+import static org.hamcrest.Matchers.containsString
+
+class JavaLibraryDistributionIntegrationTest extends WellBehavedPluginTest {
+
+    @Override
+    String getPluginId() {
+        "java-library-distribution"
+    }
+
+    def canCreateADistributionWithSrcDistRuntime() {
+        given:
+        createDir('libs') {
+            file 'a.jar'
+        }
+        createDir('src/dist') {
+            file 'file1.txt'
+            dir2 {
+                file 'file2.txt'
+            }
+        }
+        and:
+        settingsFile << "rootProject.name='canCreateADistributionWithSrcDistRuntime'"
+        and:
+        buildFile << """
+		apply plugin:'java-library-distribution'
+            distribution{
+				name ='SuperApp'
+			}
+
+			dependencies {
+				runtime files('libs/a.jar')
+			}
+        """
+        when:
+        run 'distZip'
+        then:
+        def expandDir = file('expanded')
+        file('build/distributions/SuperApp.zip').unzipTo(expandDir)
+        expandDir.assertHasDescendants('lib/a.jar', 'file1.txt', 'dir2/file2.txt', 'canCreateADistributionWithSrcDistRuntime.jar')
+    }
+
+    def canCreateADistributionWithReasonableDefaults() {
+        given:
+        createDir('libs') {
+            file 'a.jar'
+        }
+        settingsFile << "rootProject.name = 'DefaultJavaDistribution'"
+        and:
+        buildFile << """
+        apply plugin:'java-library-distribution'
+        dependencies {
+            runtime files('libs/a.jar')
+        }
+        """
+        when:
+        run 'distZip'
+
+        then:
+        def expandDir = file('expanded')
+        file('build/distributions/DefaultJavaDistribution.zip').unzipTo(expandDir)
+        expandDir.assertHasDescendants('lib/a.jar', 'DefaultJavaDistribution.jar')
+    }
+
+    def failWithNullConfiguredDistributionName() {
+        when:
+        buildFile << """
+            apply plugin:'java-library-distribution'
+            distribution{
+                name = null
+            }
+            """
+        then:
+        runAndFail 'distZip'
+        failure.assertThatDescription(containsString("Distribution name must not be null! Check your configuration of the java-library-distribution plugin."))
+    }
+
+    def canCreateADistributionIncludingOtherFile() {
+        given:
+        createDir('libs') {
+            file 'a.jar'
+        }
+        createDir('src/dist') {
+            file 'file1.txt'
+            dir2 {
+                file 'file2.txt'
+            }
+        }
+        createDir('other') {
+            file 'file3.txt'
+        }
+        createDir('other2') {
+            file 'file4.txt'
+        }
+        and:
+        settingsFile << "rootProject.name='canCreateADistributionIncludingOtherFile'"
+        and:
+        buildFile << """
+		apply plugin:'java-library-distribution'
+            distribution{
+				name ='SuperApp'
+			}
+
+			dependencies {
+				runtime files('libs/a.jar')
+			}
+
+			distZip{
+				from('other')
+				from('other2'){
+					into('other2')
+				}
+			}
+        """
+        when:
+        run 'distZip'
+        then:
+        def expandDir = file('expanded')
+        file('build/distributions/SuperApp.zip').unzipTo(expandDir)
+        expandDir.assertHasDescendants('lib/a.jar', 'file1.txt', 'dir2/file2.txt', 'canCreateADistributionIncludingOtherFile.jar', 'file3.txt', 'other2/file4.txt')
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/github/GitHubDependenciesPluginIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/github/GitHubDependenciesPluginIntegrationTest.groovy
deleted file mode 100644
index e9d3700..0000000
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/github/GitHubDependenciesPluginIntegrationTest.groovy
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.plugins.github
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-
-class GitHubDependenciesPluginIntegrationTest extends AbstractIntegrationSpec {
-
-    // Note: this is hitting the real GitHub, so will fail if GitHub is down.
-    def "can resolve from github"() {
-        given:
-        buildFile << """
-            apply plugin: 'github-dependencies'
-
-            repositories {
-                github.downloads {
-                    user "gradleware"
-                }
-            }
-
-            configurations { deps }
-
-            dependencies {
-                deps "gradle-talks:gradle-migration at zip"
-            }
-
-            task getdeps(type: Copy) {
-                from configurations.deps
-                into "deps"
-            }
-        """
-
-        when:
-        executer.withArguments("-i", "--refresh-dependencies")
-        run "getdeps"
-
-        then:
-        file("deps/gradle-migration-.zip").exists()
-    }
-
-}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/GroovyBasePluginIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/GroovyBasePluginIntegrationTest.groovy
new file mode 100644
index 0000000..629b864
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/GroovyBasePluginIntegrationTest.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.groovy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class GroovyBasePluginIntegrationTest extends AbstractIntegrationSpec {
+    def "defaults Groovy class path to inferred Groovy dependency if Groovy configuration is empty"() {
+        file("build.gradle") << """
+apply plugin: "groovy-base"
+
+sourceSets {
+    custom
+}
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    customCompile "$dependency"
+}
+
+task groovydoc(type: Groovydoc) {
+    classpath = sourceSets.custom.runtimeClasspath
+}
+
+task verify << {
+    assert compileCustomGroovy.groovyClasspath.files.any { it.name == "$jarFile" }
+    assert groovydoc.groovyClasspath.files.any { it.name == "$jarFile" }
+}
+"""
+
+        expect:
+        succeeds("verify")
+
+        where:
+        dependency                                  | jarFile
+        "org.codehaus.groovy:groovy-all:2.0.5"      | "groovy-all-2.0.5.jar"
+        "org.codehaus.groovy:groovy:2.0.5"          | "groovy-2.0.5.jar"
+        "org.codehaus.groovy:groovy-all:2.0.5:indy" | "groovy-all-2.0.5-indy.jar"
+    }
+
+    def "defaults groovyClasspath to (empty) Groovy configuration if Groovy library isn't found on class path"() {
+        file("build.gradle") << """
+apply plugin: "groovy-base"
+
+sourceSets {
+    custom
+}
+
+repositories {
+    mavenCentral()
+}
+
+task groovydoc(type: Groovydoc)
+
+task verify << {
+    assert compileCustomGroovy.groovyClasspath.is(configurations.groovy)
+    assert groovydoc.groovyClasspath.is(configurations.groovy)
+}
+"""
+
+        expect:
+        succeeds("verify")
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntForkingGroovyCompilerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntForkingGroovyCompilerIntegrationTest.groovy
index 6165ded..fb9b4ac 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntForkingGroovyCompilerIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntForkingGroovyCompilerIntegrationTest.groovy
@@ -18,7 +18,7 @@ package org.gradle.groovy.compile
 import org.gradle.integtests.fixtures.TargetVersions
 import org.gradle.util.VersionNumber
 
- at TargetVersions(['1.6.9', '1.7.11', '1.8.8', '2.0.4'])
+ at TargetVersions(['1.6.9', '1.7.11', '1.8.8', '2.0.5'])
 class AntForkingGroovyCompilerIntegrationTest extends GroovyCompilerIntegrationSpec {
 
     @Override
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntInProcessGroovyCompilerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntInProcessGroovyCompilerIntegrationTest.groovy
index c65ec63..4ba2e6e 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntInProcessGroovyCompilerIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntInProcessGroovyCompilerIntegrationTest.groovy
@@ -22,7 +22,7 @@ import org.gradle.integtests.fixtures.TargetVersions
 @TargetVersions(['1.6.9', '1.7.10', '1.7.11' , '1.8.6', '1.8.8', '2.0.5'])
 class AntInProcessGroovyCompilerIntegrationTest extends BasicGroovyCompilerIntegrationSpec {
     def setup() {
-        executer.withForkingExecuter()
+        executer.requireGradleHome(true)
     }
 
     @Override
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec.groovy
index e477d83..19f6bce 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec.groovy
@@ -16,7 +16,7 @@
 
 package org.gradle.groovy.compile
 
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 
 abstract class ApiGroovyCompilerIntegrationSpec extends GroovyCompilerIntegrationSpec {
     def canEnableAndDisableIntegerOptimization() {
@@ -53,7 +53,7 @@ abstract class ApiGroovyCompilerIntegrationSpec extends GroovyCompilerIntegratio
 
         then:
         noExceptionThrown()
-        def result = new JUnitTestExecutionResult(testDir)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted("Person")
         result.testClass("Person").assertTestPassed("testMe")
     }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec.groovy
index 88f31a5..ce6a14d 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec.groovy
@@ -15,29 +15,43 @@
  */
 package org.gradle.groovy.compile
 
-import org.gradle.integtests.fixtures.ExecutionResult
 import org.gradle.integtests.fixtures.MultiVersionIntegrationSpec
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.integtests.fixtures.TargetVersions
-import org.gradle.integtests.fixtures.ExecutionFailure
+import org.gradle.integtests.fixtures.executer.ExecutionFailure
+import org.gradle.integtests.fixtures.executer.ExecutionResult
 import org.gradle.util.VersionNumber
 import org.junit.Rule
 
 import com.google.common.collect.Ordering
 
- at TargetVersions(['1.5.8', '1.6.9', '1.7.11', '1.8.8', '2.0.4'])
+ at TargetVersions(['1.5.8', '1.6.9', '1.7.11', '1.8.8', '2.0.5'])
 abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegrationSpec {
     @Rule TestResources resources = new TestResources()
 
+    String groovyDependency = "org.codehaus.groovy:groovy-all:$version"
+
     def setup() {
-        executer.withArguments("-i")
+        // necessary for picking up some of the output/errorOutput when forked executer is used
+        executer.withArgument("-i")
     }
 
-    def "badCodeBreaksBuild"() {
-        when:
-        runAndFail("classes")
+    def "compileGoodCode"() {
+        groovyDependency = "org.codehaus.groovy:$module:$version"
+
+        expect:
+        succeeds("compileGroovy")
+        !errorOutput
+        file("build/classes/main/Person.class").exists()
+        file("build/classes/main/Address.class").exists()
+
+        where:
+        module << ["groovy-all", "groovy"]
+    }
 
-        then:
+    def "compileBadCode"() {
+        expect:
+        fails("compileGroovy")
         // for some reasons, line breaks occur in different places when running this
         // test in different environments; hence we only check for short snippets
         compileErrorOutput.contains 'unable'
@@ -47,58 +61,60 @@ abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegratio
         failure.assertHasCause(compilationFailureMessage)
     }
 
-    def "badJavaCodeBreaksBuild"() {
-        when:
-        runAndFail("classes")
-
-        then:
+    def "compileBadJavaCode"() {
+        expect:
+        fails("compileGroovy")
         compileErrorOutput.contains 'illegal start of type'
         failure.assertHasCause(compilationFailureMessage)
     }
 
     def "canCompileAgainstGroovyClassThatDependsOnExternalClass"() {
         if (getClass() == AntInProcessGroovyCompilerIntegrationTest &&
-                (version == '1.7.11' || versionNumber >= VersionNumber.parse('1.8.7'))) {
-            return // known not to work; see comment on GRADLE-2404
+                (version == '1.6.9' || version == '1.7.11' || versionNumber >= VersionNumber.parse('1.8.7'))) {
+            // known not to work in 1.7.11, 1.8.7 and beyond (see comment on GRADLE-2404)
+            // only works with 1.6.9 if JUnit makes it on Ant (!) class path, which is no longer the case
+            // note that these problems only apply to useAnt=true; fork=false
+            return
         }
 
-        when:
-        run("test")
-
-        then:
-        noExceptionThrown()
+        expect:
+        succeeds("test")
     }
 
     def "canListSourceFiles"() {
-        when:
-        run("compileGroovy")
-
-        then:
+        expect:
+        succeeds("compileGroovy")
         output.contains(new File("src/main/groovy/compile/test/Person.groovy").toString())
         output.contains(new File("src/main/groovy/compile/test/Person2.groovy").toString())
         !errorOutput
-        file("build/classes/main/compile/test/Person.class").exists()
-        file("build/classes/main/compile/test/Person2.class").exists()
     }
 
-    @Override
     protected ExecutionResult run(String... tasks) {
-        tweakBuildFile()
-        return super.run(tasks)
+        configureGroovy()
+        super.run(tasks)
     }
 
-    @Override
     protected ExecutionFailure runAndFail(String... tasks) {
-        tweakBuildFile()
-        return super.runAndFail(tasks)
+        configureGroovy()
+        super.runAndFail(tasks)
     }
 
-    private void tweakBuildFile() {
-        buildFile << """
-dependencies { groovy 'org.codehaus.groovy:groovy-all:$version' }
-        """
+    protected ExecutionResult succeeds(String... tasks) {
+        configureGroovy()
+        super.succeeds(tasks)
+    }
 
+    protected ExecutionFailure fails(String... tasks) {
+        configureGroovy()
+        super.fails(tasks)
+    }
+
+    private void configureGroovy() {
         buildFile << """
+dependencies {
+    compile '${groovyDependency.toString()}'
+}
+
 DeprecationLogger.whileDisabled {
     ${compilerConfiguration()}
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest.groovy
index 1a3aaf3..61ccb1c 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest.groovy
@@ -15,23 +15,21 @@
  */
 package org.gradle.groovy.compile
 
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.executer.ExecutionFailure
 import org.junit.Rule
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.GradleDistribution
 import org.junit.Test
-import org.gradle.integtests.fixtures.ExecutionFailure
 
-class IncrementalGroovyCompileIntegrationTest {
-    @Rule public final GradleDistribution distribution = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class IncrementalGroovyCompileIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final TestResources resources = new TestResources()
 
     @Test
     public void recompilesSourceWhenPropertiesChange() {
         executer.withTasks('compileGroovy').run().assertTasksSkipped(':compileJava')
 
-        distribution.testFile('build.gradle').text += '''
+        file('build.gradle').text += '''
             compileGroovy.options.debug = false
 '''
 
@@ -45,7 +43,7 @@ class IncrementalGroovyCompileIntegrationTest {
         executer.withTasks("classes").run();
 
         // Update interface, compile should fail
-        distribution.testFile('src/main/groovy/IPerson.groovy').assertIsFile().copyFrom(distribution.testFile('NewIPerson.groovy'))
+        file('src/main/groovy/IPerson.groovy').assertIsFile().copyFrom(file('NewIPerson.groovy'))
 
         ExecutionFailure failure = executer.withTasks("classes").runWithFailure();
         failure.assertHasDescription("Execution failed for task ':compileGroovy'.");
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InvokeDynamicGroovyCompilerSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InvokeDynamicGroovyCompilerSpec.groovy
index e6c2db0..bfa0858 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InvokeDynamicGroovyCompilerSpec.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InvokeDynamicGroovyCompilerSpec.groovy
@@ -21,7 +21,7 @@ import org.gradle.util.TestPrecondition
 import org.gradle.integtests.fixtures.TargetVersions
 
 @Requires(TestPrecondition.JDK7)
- at TargetVersions(['2.0.4:indy'])
+ at TargetVersions(['2.0.5:indy'])
 class InvokeDynamicGroovyCompilerSpec extends ApiGroovyCompilerIntegrationSpec {
     def canEnableAndDisableInvokeDynamicOptimization() {
         when:
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/environment/JreJavaHomeGroovyIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/environment/JreJavaHomeGroovyIntegrationTest.groovy
index 6061771..591a545 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/environment/JreJavaHomeGroovyIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/environment/JreJavaHomeGroovyIntegrationTest.groovy
@@ -16,12 +16,12 @@
 
 package org.gradle.groovy.environment
 
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.AvailableJavaHomes
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
 import spock.lang.IgnoreIf
 import spock.lang.Unroll
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 
 class JreJavaHomeGroovyIntegrationTest extends AbstractIntegrationSpec {
 
@@ -74,7 +74,7 @@ class JreJavaHomeGroovyIntegrationTest extends AbstractIntegrationSpec {
             }
             """
         when:
-        def envVars = System.getenv().findAll { it.key != 'JAVA_HOME' || it.key != 'Path'}
+        def envVars = System.getenv().findAll { !(it.key in ['GRADLE_OPTS', 'JAVA_HOME', 'Path']) }
         envVars.put("Path", "C:\\Windows\\System32")
         executer.withEnvironmentVars(envVars).withTasks("compileGroovy").run()
 
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest.groovy
index 5f00d71..e5feead 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest.groovy
@@ -15,29 +15,27 @@
  */
 package org.gradle.java.compile
 
-import org.junit.Rule
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.TestResources
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.executer.ExecutionFailure
+import org.junit.Rule
 import org.junit.Test
-import org.gradle.integtests.fixtures.ExecutionFailure
 
-class IncrementalJavaCompileIntegrationTest {
-    @Rule public final GradleDistribution distribution = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class IncrementalJavaCompileIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final TestResources resources = new TestResources()
 
     @Test
     public void recompilesSourceWhenPropertiesChange() {
         executer.withTasks('compileJava').run().assertTasksSkipped()
 
-        distribution.testFile('build.gradle').text += '''
+        file('build.gradle').text += '''
             sourceCompatibility = 1.4
 '''
 
         executer.withTasks('compileJava').run().assertTasksSkipped()
 
-        distribution.testFile('build.gradle').text += '''
+        file('build.gradle').text += '''
             compileJava.options.debug = false
 '''
 
@@ -51,7 +49,7 @@ class IncrementalJavaCompileIntegrationTest {
         executer.withTasks("classes").run();
 
         // Update interface, compile should fail
-        distribution.testFile('src/main/java/IPerson.java').assertIsFile().copyFrom(distribution.testFile('NewIPerson.java'))
+        file('src/main/java/IPerson.java').assertIsFile().copyFrom(file('NewIPerson.java'))
         
         ExecutionFailure failure = executer.withTasks("classes").runWithFailure();
         failure.assertHasDescription("Execution failed for task ':compileJava'.");
@@ -62,7 +60,7 @@ class IncrementalJavaCompileIntegrationTest {
         executer.withTasks("app:classes").run();
 
         // Update interface, compile should fail
-        distribution.testFile('lib/src/main/java/IPerson.java').assertIsFile().copyFrom(distribution.testFile('NewIPerson.java'))
+        file('lib/src/main/java/IPerson.java').assertIsFile().copyFrom(file('NewIPerson.java'))
 
         ExecutionFailure failure = executer.withTasks("app:classes").runWithFailure();
         failure.assertHasDescription("Execution failed for task ':app:compileJava'.");
@@ -70,7 +68,7 @@ class IncrementalJavaCompileIntegrationTest {
 
     @Test
     public void recompilesDependentClassesWhenUsingAntDepend() {
-        distribution.testFile("build.gradle").writelns(
+        file("build.gradle").writelns(
                 "apply plugin: 'java'",
                 "compileJava.options.depend()"
         );
@@ -88,11 +86,11 @@ class IncrementalJavaCompileIntegrationTest {
         failure.assertHasDescription("Execution failed for task ':compileJava'.");
 
         // assert that dependency caching is on
-        distribution.testFile("build/dependency-cache/dependencies.txt").assertExists();
+        file("build/dependency-cache/dependencies.txt").assertExists();
     }
 
     private void writeShortInterface() {
-        distribution.testFile("src/main/java/IPerson.java").writelns(
+        file("src/main/java/IPerson.java").writelns(
                 "interface IPerson {",
                 "    String getName();",
                 "}"
@@ -100,7 +98,7 @@ class IncrementalJavaCompileIntegrationTest {
     }
 
     private void writeLongInterface() {
-        distribution.testFile("src/main/java/IPerson.java").writelns(
+        file("src/main/java/IPerson.java").writelns(
                 "interface IPerson {",
                 "    String getName();",
                 "    String getAddress();",
@@ -109,7 +107,7 @@ class IncrementalJavaCompileIntegrationTest {
     }
 
     private void writeTestClass() {
-        distribution.testFile("src/main/java/Person.java").writelns(
+        file("src/main/java/Person.java").writelns(
                 "public class Person implements IPerson {",
                 "    private final String name = \"never changes\";",
                 "    public String getName() {",
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/environment/JreJavaHomeJavaIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/environment/JreJavaHomeJavaIntegrationTest.groovy
index 1014e0b..7b5dba7 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/java/environment/JreJavaHomeJavaIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/environment/JreJavaHomeJavaIntegrationTest.groovy
@@ -61,7 +61,7 @@ class JreJavaHomeJavaIntegrationTest extends AbstractIntegrationSpec {
                         DeprecationLogger.whileDisabled { options.useAnt = ${useAnt} }
         }
         """
-        def envVars = System.getenv().findAll { it.key != 'JAVA_HOME' || it.key != 'Path'}
+        def envVars = System.getenv().findAll { !(it.key in ['GRADLE_OPTS', 'JAVA_HOME', 'Path']) }
         envVars.put("Path", "C:\\Windows\\System32")
         when:
         executer.withEnvironmentVars(envVars).withTasks("compileJava").run()
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/javadoc/JavadocIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/javadoc/JavadocIntegrationTest.groovy
index 4b74fac..daa93e8 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/javadoc/JavadocIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/javadoc/JavadocIntegrationTest.groovy
@@ -15,9 +15,9 @@
  */
 package org.gradle.javadoc
 
-import org.junit.Rule
-import org.gradle.integtests.fixtures.TestResources
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.Rule
 import spock.lang.Issue
 
 class JavadocIntegrationTest extends AbstractIntegrationSpec {
@@ -34,4 +34,15 @@ class JavadocIntegrationTest extends AbstractIntegrationSpec {
         // we can't currently control the order between tags and taglets (limitation on our side)
         javadoc.text =~ /(?ms)Custom Taglet.*custom taglet value/
     }
+
+    @Issue("GRADLE-2520")
+    def canCombineLocalOptionWithOtherOptions() {
+        when:
+        run("javadoc")
+
+        then:
+        def javadoc = testResources.dir.file("build/docs/javadoc/Person.html")
+        javadoc.text =~ /(?ms)USED LOCALE=de_DE/
+        javadoc.text =~ /(?ms)Serial no. is valid javadoc!/
+    }
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestEnvironmentIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestEnvironmentIntegrationTest.groovy
index 4d6e785..2451ced 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestEnvironmentIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestEnvironmentIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.testing
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.TestResources
 import org.junit.Rule
 
@@ -29,7 +29,7 @@ class TestEnvironmentIntegrationTest extends AbstractIntegrationSpec {
         run 'test'
 
         then:
-        def result = new JUnitTestExecutionResult(testDir)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.JUnitTest')
         result.testClass('org.gradle.JUnitTest').assertTestPassed('mySystemClassLoaderIsUsed')
     }
@@ -39,7 +39,7 @@ class TestEnvironmentIntegrationTest extends AbstractIntegrationSpec {
         run 'test'
 
         then:
-        def result = new JUnitTestExecutionResult(testDir)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.JUnitTest')
         result.testClass('org.gradle.JUnitTest').assertTestPassed('mySystemClassLoaderIsUsed')
     }
@@ -49,7 +49,7 @@ class TestEnvironmentIntegrationTest extends AbstractIntegrationSpec {
         run 'test'
 
         then:
-        def result = new JUnitTestExecutionResult(testDir)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.JUnitTest')
         result.testClass('org.gradle.JUnitTest').assertTestPassed('mySecurityManagerIsUsed')
     }
@@ -59,7 +59,7 @@ class TestEnvironmentIntegrationTest extends AbstractIntegrationSpec {
         run 'test'
 
         then:
-        def result = new JUnitTestExecutionResult(testDir)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.JMockitTest')
         result.testClass('org.gradle.JMockitTest').assertTestPassed('testOk')
     }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestOutputListenerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestOutputListenerIntegrationTest.groovy
index d74de2f..dae72cd 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestOutputListenerIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestOutputListenerIntegrationTest.groovy
@@ -56,7 +56,7 @@ public class SomeTest {
         buildFile << """
 apply plugin: 'java'
 repositories { mavenCentral() }
-dependencies { testCompile "junit:junit:4.8.2" }
+dependencies { testCompile "junit:junit:4.11" }
 
 test.addTestOutputListener(new VerboseOutputListener(logger: project.logger))
 
@@ -109,7 +109,7 @@ public class SomeTest {
         buildFile << """
 apply plugin: 'java'
 repositories { mavenCentral() }
-dependencies { testCompile "junit:junit:4.8.2" }
+dependencies { testCompile "junit:junit:4.11" }
 
 test.onOutput { descriptor, event ->
     logger.lifecycle("first: " + event.message)
@@ -152,7 +152,7 @@ public class SomeTest {
         buildFile << """
 apply plugin: 'java'
 repositories { mavenCentral() }
-dependencies { testCompile "junit:junit:4.8.2" }
+dependencies { testCompile "junit:junit:4.11" }
 
 test.testLogging {
     showStandardStreams = true
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestReportIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestReportIntegrationTest.groovy
new file mode 100644
index 0000000..3e8c66e
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestReportIntegrationTest.groovy
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.testing
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.junit.Rule
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.integtests.fixtures.UsesSample
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import static org.hamcrest.Matchers.equalTo
+
+class TestReportIntegrationTest extends AbstractIntegrationSpec {
+    @Rule Sample sample
+
+    def "report includes results of each invocation"() {
+        given:
+        buildFile << """
+apply plugin: 'java'
+repositories { mavenCentral() }
+dependencies { testCompile 'junit:junit:4.11' }
+test { systemProperty 'LogLessStuff', System.getProperty('LogLessStuff') }
+"""
+
+        and:
+        file("src/test/java/LoggingTest.java") << """
+public class LoggingTest {
+    @org.junit.Test
+    public void test() {
+        if (System.getProperty("LogLessStuff", "false").equals("true")) {
+            System.out.print("stdout.");
+            System.err.print("stderr.");
+        } else {
+            System.out.print("This is stdout.");
+            System.err.print("This is stderr.");
+        }
+    }
+}
+"""
+
+        when:
+        run "test"
+
+        then:
+        def result = new DefaultTestExecutionResult(testDirectory)
+        result.testClass("LoggingTest").assertStdout(equalTo("This is stdout."))
+        result.testClass("LoggingTest").assertStderr(equalTo("This is stderr."))
+
+        when:
+        executer.withArguments("-DLogLessStuff=true")
+        run "test"
+
+        then:
+        result.testClass("LoggingTest").assertStdout(equalTo("stdout."))
+        result.testClass("LoggingTest").assertStderr(equalTo("stderr."))
+    }
+
+    @UsesSample("testing/testReport")
+    def "can generate report for subprojects"() {
+        given:
+        sample sample
+
+        when:
+        run "testReport"
+
+        then:
+        def reportDir = sample.dir.file("build/reports/allTests")
+        reportDir.file("index.html").assertIsFile()
+        reportDir.file("org.gradle.sample.CoreTest.html").text.contains("hello from CoreTest.")
+        reportDir.file("org.gradle.sample.UtilTest.html").text.contains("hello from UtilTest.")
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestingIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestingIntegrationTest.groovy
index a495b93..66760f1 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestingIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestingIntegrationTest.groovy
@@ -16,7 +16,7 @@
 package org.gradle.testing
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import spock.lang.Issue
 import spock.lang.Timeout
 import spock.lang.Unroll
@@ -33,7 +33,7 @@ class TestingIntegrationTest extends AbstractIntegrationSpec {
         buildFile << """
             apply plugin: 'java'
             repositories { mavenCentral() }
-            dependencies { testCompile "junit:junit:4.8.2" }
+            dependencies { testCompile "junit:junit:4.11" }
         """
 
         and:
@@ -77,7 +77,7 @@ class TestingIntegrationTest extends AbstractIntegrationSpec {
 
         where:
         framework   | dependency                | superClass
-        "useJUnit"  | "junit:junit:4.10"        | "org.junit.runner.Result"
+        "useJUnit"  | "junit:junit:4.11"        | "org.junit.runner.Result"
         "useTestNG" | "org.testng:testng:6.3.1" | "org.testng.Converter"
     }
 
@@ -97,7 +97,7 @@ class TestingIntegrationTest extends AbstractIntegrationSpec {
                 }
 
                 dependencies{
-	                othertestsCompile "junit:junit:4.10"
+	                othertestsCompile "junit:junit:4.11"
                 }
 
                 task othertestsTest(type:Test){
@@ -126,7 +126,7 @@ class TestingIntegrationTest extends AbstractIntegrationSpec {
         when:
         run "othertestsTest"
         then:
-        def result = new JUnitTestExecutionResult(distribution.testDir)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted("TestCaseExtendsAbstractClass")
     }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitCrossVersionIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitCrossVersionIntegrationSpec.groovy
new file mode 100644
index 0000000..6d673e4
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitCrossVersionIntegrationSpec.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.testing.junit
+
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.MultiVersionIntegrationSpec
+import org.gradle.integtests.fixtures.TargetVersions
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.Rule
+import org.junit.Test
+
+ at TargetVersions(['4,0', '4.4', '4.8.2', '4.11'])
+class JUnitCrossVersionIntegrationSpec extends MultiVersionIntegrationSpec {
+    @Rule
+    public final TestResources resources = new TestResources()
+
+
+    String junitDependency = "junit:junit:$version"
+
+    @Test
+    public void canRunTestsUsingJUnit() {
+        given:
+        resources.maybeCopy('JUnitIntegrationTest/junit3Tests')
+        resources.maybeCopy('JUnitIntegrationTest/junit4Tests')
+
+        configureJUnit();
+        when:
+        executer.withTasks('check').run()
+        then:
+        def result = new DefaultTestExecutionResult(testDirectory)
+        result.assertTestClassesExecuted('org.gradle.Junit3Test', 'org.gradle.Junit4Test', 'org.gradle.IgnoredTest', 'org.gradle.CustomIgnoredTest')
+        result.testClass('org.gradle.Junit3Test').assertTestsExecuted('testRenamesItself')
+        result.testClass('org.gradle.Junit3Test').assertTestPassed('testRenamesItself')
+        result.testClass('org.gradle.Junit4Test').assertTestsExecuted('ok')
+        result.testClass('org.gradle.Junit4Test').assertTestPassed('ok')
+        result.testClass('org.gradle.Junit4Test').assertTestsSkipped('broken')
+        result.testClass('org.gradle.IgnoredTest').assertTestsSkipped('testIgnored')
+        result.testClass('org.gradle.CustomIgnoredTest').assertTestCount(3, 0, 0).assertTestsSkipped("first test run", "second test run", "third test run")
+    }
+
+    private void configureJUnit() {
+        buildFile << """
+        dependencies {
+        testCompile '${junitDependency.toString()}'
+        }"""
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIntegrationTest.groovy
index 75b0626..e9cb334 100755
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIntegrationTest.groovy
@@ -15,18 +15,23 @@
  */
 package org.gradle.testing.junit
 
-import org.gradle.util.TestFile
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.executer.ExecutionResult
+import org.gradle.test.fixtures.file.TestFile
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
-import org.gradle.integtests.fixtures.*
 
 import static org.gradle.util.Matchers.containsLine
+import static org.gradle.util.Matchers.containsText
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertThat
 
 public class JUnitIntegrationTest extends AbstractIntegrationTest {
-    @Rule public final TestResources resources = new TestResources()
+    @Rule
+    public final TestResources resources = new TestResources()
 
     @Before
     public void before() {
@@ -37,11 +42,12 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
     public void executesTestsInCorrectEnvironment() {
         executer.withTasks('build').run();
 
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.OkTest', 'org.gradle.OtherTest')
 
         result.testClass('org.gradle.OkTest').assertTestPassed('ok')
         result.testClass('org.gradle.OkTest').assertStdout(containsString('This is test stdout'))
+        result.testClass('org.gradle.OkTest').assertStdout(containsString('non-asci char: ż'))
         result.testClass('org.gradle.OkTest').assertStdout(containsString('no EOL'))
         result.testClass('org.gradle.OkTest').assertStdout(containsString('class loaded'))
         result.testClass('org.gradle.OkTest').assertStdout(containsString('test constructed'))
@@ -68,15 +74,15 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
     public void suitesOutputIsVisible() {
         executer.withTasks('test').withArguments('-i').run();
 
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.ASuite')
-
         result.testClass('org.gradle.ASuite').assertStdout(containsString('suite class loaded'))
         result.testClass('org.gradle.ASuite').assertStderr(containsString('This is test stderr'))
         result.testClass('org.gradle.ASuite').assertStdout(containsString('sys out from another test method'))
         result.testClass('org.gradle.ASuite').assertStderr(containsString('sys err from another test method'))
         result.testClass('org.gradle.ASuite').assertStdout(containsString('This is other stdout'))
         result.testClass('org.gradle.ASuite').assertStdout(containsString('before suite class out'))
+        result.testClass('org.gradle.ASuite').assertStdout(containsString('non-asci char: ż'))
         result.testClass('org.gradle.ASuite').assertStderr(containsString('before suite class err'))
         result.testClass('org.gradle.ASuite').assertStdout(containsString('after suite class out'))
         result.testClass('org.gradle.ASuite').assertStderr(containsString('after suite class err'))
@@ -88,8 +94,8 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
         resources.maybeCopy('JUnitIntegrationTest/junit4Tests')
         executer.withTasks('check').run()
 
-        def result = new JUnitTestExecutionResult(testDir)
-        result.assertTestClassesExecuted('org.gradle.Junit3Test', 'org.gradle.Junit4Test', 'org.gradle.IgnoredTest')
+        def result = new DefaultTestExecutionResult(testDirectory)
+        result.assertTestClassesExecuted('org.gradle.Junit3Test', 'org.gradle.Junit4Test', 'org.gradle.IgnoredTest', 'org.gradle.CustomIgnoredTest')
         result.testClass('org.gradle.Junit3Test')
                 .assertTestCount(1, 0, 0)
                 .assertTestsExecuted('testRenamesItself')
@@ -99,7 +105,8 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
                 .assertTestsExecuted('ok')
                 .assertTestPassed('ok')
                 .assertTestsSkipped('broken')
-        result.testClass('org.gradle.IgnoredTest').assertTestCount(0, 0, 0).assertTestsExecuted()
+        result.testClass('org.gradle.IgnoredTest').assertTestCount(1, 0, 0).assertTestsSkipped("testIgnored")
+        result.testClass('org.gradle.CustomIgnoredTest').assertTestCount(3, 0, 0).assertTestsSkipped("first test run", "second test run", "third test run")
     }
 
     @Test
@@ -107,39 +114,20 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
         resources.maybeCopy('JUnitIntegrationTest/junit3Tests')
         executer.withTasks('check').run()
 
-        def result = new JUnitTestExecutionResult(testDir)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.Junit3Test')
         result.testClass('org.gradle.Junit3Test').assertTestsExecuted('testRenamesItself')
         result.testClass('org.gradle.Junit3Test').assertTestPassed('testRenamesItself')
     }
 
     @Test
-    public void canRunTestsUsingJUnit4_4() {
-        resources.maybeCopy('JUnitIntegrationTest/junit3Tests')
-        resources.maybeCopy('JUnitIntegrationTest/junit4Tests')
-        resources.maybeCopy('JUnitIntegrationTest/junit4_4Tests')
-        executer.withTasks('check').run()
-
-        def result = new JUnitTestExecutionResult(testDir)
-        result.assertTestClassesExecuted('org.gradle.Junit3Test', 'org.gradle.Junit4Test', 'org.gradle.IgnoredTest')
-        result.testClass('org.gradle.Junit3Test').assertTestsExecuted('testRenamesItself')
-        result.testClass('org.gradle.Junit3Test').assertTestPassed('testRenamesItself')
-        result.testClass('org.gradle.Junit4Test').assertTestsExecuted('ok')
-        result.testClass('org.gradle.Junit4Test').assertTestPassed('ok')
-        result.testClass('org.gradle.Junit4Test').assertTestsSkipped('broken')
-        result.testClass('org.gradle.IgnoredTest').assertTestsExecuted()
-    }
-
-    @Test
     public void reportsAndBreaksBuildWhenTestFails() {
-        ExecutionFailure failure = executer.withTasks('build').runWithFailure();
+        executer.withTasks('build').runWithFailure().assertTestsFailed()
 
-        failure.assertHasDescription("Execution failed for task ':test'.");
-        failure.assertThatCause(startsWith('There were failing tests.'));
-
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted(
                 'org.gradle.ClassWithBrokenRunner',
+                'org.gradle.CustomException',
                 'org.gradle.BrokenTest',
                 'org.gradle.BrokenBefore',
                 'org.gradle.BrokenAfter',
@@ -148,26 +136,29 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
                 'org.gradle.BrokenBeforeAndAfter',
                 'org.gradle.BrokenConstructor',
                 'org.gradle.BrokenException',
-                'org.gradle.Unloadable')
+                'org.gradle.Unloadable',
+                'org.gradle.UnserializableException')
         result.testClass('org.gradle.ClassWithBrokenRunner').assertTestFailed('initializationError', equalTo('java.lang.UnsupportedOperationException: broken'))
         result.testClass('org.gradle.BrokenTest')
                 .assertTestCount(2, 2, 0)
                 .assertTestFailed('failure', equalTo('java.lang.AssertionError: failed'))
-                .assertTestFailed('broken', equalTo('java.lang.IllegalStateException'))
+                .assertTestFailed('broken', equalTo('java.lang.IllegalStateException: html: <> cdata: ]]>'))
         result.testClass('org.gradle.BrokenBeforeClass').assertTestFailed('classMethod', equalTo('java.lang.AssertionError: failed'))
         result.testClass('org.gradle.BrokenAfterClass').assertTestFailed('classMethod', equalTo('java.lang.AssertionError: failed'))
         result.testClass('org.gradle.BrokenBefore').assertTestFailed('ok', equalTo('java.lang.AssertionError: failed'))
         result.testClass('org.gradle.BrokenAfter').assertTestFailed('ok', equalTo('java.lang.AssertionError: failed'))
         result.testClass('org.gradle.BrokenBeforeAndAfter').assertTestFailed('ok', equalTo('java.lang.AssertionError: before failed'), equalTo('java.lang.AssertionError: after failed'))
         result.testClass('org.gradle.BrokenConstructor').assertTestFailed('ok', equalTo('java.lang.AssertionError: failed'))
-        result.testClass('org.gradle.BrokenException').assertTestFailed('broken', startsWith('Could not determine failure message for exception of type org.gradle.BrokenException$BrokenRuntimeException: '))
+        result.testClass('org.gradle.BrokenException').assertTestFailed('broken', startsWith('Could not determine failure message for exception of type org.gradle.BrokenException$BrokenRuntimeException: java.lang.UnsupportedOperationException'))
+        result.testClass('org.gradle.CustomException').assertTestFailed('custom', startsWith('Exception with a custom toString implementation'))
         result.testClass('org.gradle.Unloadable').assertTestFailed('initializationError', equalTo('java.lang.AssertionError: failed'))
+        result.testClass('org.gradle.UnserializableException').assertTestFailed('unserialized', equalTo('org.gradle.UnserializableException$UnserializableRuntimeException: whatever'))
     }
 
     @Test
     public void canRunSingleTests() {
         executer.withTasks('test').withArguments('-Dtest.single=Ok2').run()
-        def result = new JUnitTestExecutionResult(testDir)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('Ok2')
 
         executer.withTasks('cleanTest', 'test').withArguments('-Dtest.single=Ok').run()
@@ -182,25 +173,25 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
 
     @Test
     public void canUseTestSuperClassesFromAnotherProject() {
-        testDir.file('settings.gradle').write("include 'a', 'b'");
-        testDir.file('b/build.gradle') << '''
+        testDirectory.file('settings.gradle').write("include 'a', 'b'");
+        testDirectory.file('b/build.gradle') << '''
             apply plugin: 'java'
             repositories { mavenCentral() }
             dependencies { compile 'junit:junit:4.7' }
         '''
-        testDir.file('b/src/main/java/org/gradle/AbstractTest.java') << '''
+        testDirectory.file('b/src/main/java/org/gradle/AbstractTest.java') << '''
             package org.gradle;
             public abstract class AbstractTest {
                 @org.junit.Test public void ok() { }
             }
         '''
-        TestFile buildFile = testDir.file('a/build.gradle');
+        TestFile buildFile = testDirectory.file('a/build.gradle');
         buildFile << '''
             apply plugin: 'java'
             repositories { mavenCentral() }
             dependencies { testCompile project(':b') }
         '''
-        testDir.file('a/src/test/java/org/gradle/SomeTest.java') << '''
+        testDirectory.file('a/src/test/java/org/gradle/SomeTest.java') << '''
             package org.gradle;
             public class SomeTest extends AbstractTest {
             }
@@ -208,27 +199,27 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
 
         executer.withTasks('a:test').run();
 
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir.file('a'))
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(testDirectory.file('a'))
         result.assertTestClassesExecuted('org.gradle.SomeTest')
         result.testClass('org.gradle.SomeTest').assertTestPassed('ok')
     }
 
     @Test
     public void canExcludeSuperClassesFromExecution() {
-        TestFile buildFile = testDir.file('build.gradle');
+        TestFile buildFile = testDirectory.file('build.gradle');
         buildFile << '''
             apply plugin: 'java'
             repositories { mavenCentral() }
             dependencies { testCompile 'junit:junit:4.7' }
             test { exclude '**/BaseTest.*' }
         '''
-        testDir.file('src/test/java/org/gradle/BaseTest.java') << '''
+        testDirectory.file('src/test/java/org/gradle/BaseTest.java') << '''
             package org.gradle;
             public class BaseTest {
                 @org.junit.Test public void ok() { }
             }
         '''
-        testDir.file('src/test/java/org/gradle/SomeTest.java') << '''
+        testDirectory.file('src/test/java/org/gradle/SomeTest.java') << '''
             package org.gradle;
             public class SomeTest extends BaseTest {
             }
@@ -236,7 +227,7 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
 
         executer.withTasks('test').run();
 
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.SomeTest')
         result.testClass('org.gradle.SomeTest').assertTestPassed('ok')
     }
@@ -245,7 +236,7 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
     public void detectsTestClasses() {
         executer.withTasks('test').run()
 
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.EmptyRunWithSubclass', 'org.gradle.TestsOnInner', 'org.gradle.TestsOnInner$SomeInner')
         result.testClass('org.gradle.EmptyRunWithSubclass').assertTestsExecuted('ok')
         result.testClass('org.gradle.EmptyRunWithSubclass').assertTestPassed('ok')
@@ -255,12 +246,12 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
 
     @Test
     public void runsAllTestsInTheSameForkedJvm() {
-        testDir.file('build.gradle').writelns(
+        testDirectory.file('build.gradle').writelns(
                 "apply plugin: 'java'",
                 "repositories { mavenCentral() }",
                 "dependencies { compile 'junit:junit:4.7' }"
         );
-        testDir.file('src/test/java/org/gradle/AbstractTest.java').writelns(
+        testDirectory.file('src/test/java/org/gradle/AbstractTest.java').writelns(
                 "package org.gradle;",
                 "public abstract class AbstractTest {",
                 "    @org.junit.Test public void ok() {",
@@ -268,19 +259,19 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
                 "        System.out.println(String.format(\"VM START TIME = %s\", time));",
                 "    }",
                 "}");
-        testDir.file('src/test/java/org/gradle/SomeTest.java').writelns(
+        testDirectory.file('src/test/java/org/gradle/SomeTest.java').writelns(
                 "package org.gradle;",
                 "public class SomeTest extends AbstractTest {",
                 "}");
-        testDir.file('src/test/java/org/gradle/SomeTest2.java').writelns(
+        testDirectory.file('src/test/java/org/gradle/SomeTest2.java').writelns(
                 "package org.gradle;",
                 "public class SomeTest2 extends AbstractTest {",
                 "}");
 
         executer.withTasks('test').run();
 
-        TestFile results1 = testDir.file('build/test-results/TEST-org.gradle.SomeTest.xml');
-        TestFile results2 = testDir.file('build/test-results/TEST-org.gradle.SomeTest2.xml');
+        TestFile results1 = testDirectory.file('build/test-results/TEST-org.gradle.SomeTest.xml');
+        TestFile results2 = testDirectory.file('build/test-results/TEST-org.gradle.SomeTest2.xml');
         results1.assertIsFile();
         results2.assertIsFile();
         assertThat(results1.linesThat(containsString('VM START TIME =')).get(0), equalTo(results2.linesThat(containsString('VM START TIME =')).get(0)));
@@ -288,13 +279,13 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
 
     @Test
     public void canSpecifyMaximumNumberOfTestClassesToExecuteInAForkedJvm() {
-        testDir.file('build.gradle').writelns(
+        testDirectory.file('build.gradle').writelns(
                 "apply plugin: 'java'",
                 "repositories { mavenCentral() }",
                 "dependencies { compile 'junit:junit:4.7' }",
                 "test.forkEvery = 1"
         );
-        testDir.file('src/test/java/org/gradle/AbstractTest.java').writelns(
+        testDirectory.file('src/test/java/org/gradle/AbstractTest.java').writelns(
                 "package org.gradle;",
                 "public abstract class AbstractTest {",
                 "    @org.junit.Test public void ok() {",
@@ -302,19 +293,19 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
                 "        System.out.println(String.format(\"VM START TIME = %s\", time));",
                 "    }",
                 "}");
-        testDir.file('src/test/java/org/gradle/SomeTest.java').writelns(
+        testDirectory.file('src/test/java/org/gradle/SomeTest.java').writelns(
                 "package org.gradle;",
                 "public class SomeTest extends AbstractTest {",
                 "}");
-        testDir.file('src/test/java/org/gradle/SomeTest2.java').writelns(
+        testDirectory.file('src/test/java/org/gradle/SomeTest2.java').writelns(
                 "package org.gradle;",
                 "public class SomeTest2 extends AbstractTest {",
                 "}");
 
         executer.withTasks('test').run();
 
-        TestFile results1 = testDir.file('build/test-results/TEST-org.gradle.SomeTest.xml');
-        TestFile results2 = testDir.file('build/test-results/TEST-org.gradle.SomeTest2.xml');
+        TestFile results1 = testDirectory.file('build/test-results/TEST-org.gradle.SomeTest.xml');
+        TestFile results2 = testDirectory.file('build/test-results/TEST-org.gradle.SomeTest2.xml');
         results1.assertIsFile();
         results2.assertIsFile();
         assertThat(results1.linesThat(containsString('VM START TIME =')).get(0), not(equalTo(results2.linesThat(
@@ -323,24 +314,24 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
 
     @Test
     public void canListenForTestResults() {
-        testDir.file('src/main/java/AppException.java').writelns(
+        testDirectory.file('src/main/java/AppException.java').writelns(
                 "public class AppException extends Exception { }"
         );
 
-        testDir.file('src/test/java/SomeTest.java').writelns(
+        testDirectory.file('src/test/java/SomeTest.java').writelns(
                 "public class SomeTest {",
                 "@org.junit.Test public void fail() { org.junit.Assert.fail(\"message\"); }",
                 "@org.junit.Test public void knownError() { throw new RuntimeException(\"message\"); }",
                 "@org.junit.Test public void unknownError() throws AppException { throw new AppException(); }",
                 "}"
         );
-        testDir.file('src/test/java/SomeOtherTest.java').writelns(
+        testDirectory.file('src/test/java/SomeOtherTest.java').writelns(
                 "public class SomeOtherTest {",
                 "@org.junit.Test public void pass() { }",
                 "}"
         );
 
-        testDir.file('build.gradle') << '''
+        testDirectory.file('build.gradle') << '''
             apply plugin: 'java'
             repositories { mavenCentral() }
             dependencies { testCompile 'junit:junit:4.7' }
@@ -374,12 +365,12 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
         assert containsLine(result.getOutput(), "START [test knownError(SomeTest)] [knownError]");
         assert containsLine(result.getOutput(), "FINISH [test knownError(SomeTest)] [knownError] [FAILURE] [1] [java.lang.RuntimeException: message]");
         assert containsLine(result.getOutput(), "START [test unknownError(SomeTest)] [unknownError]");
-        assert containsLine(result.getOutput(), "FINISH [test unknownError(SomeTest)] [unknownError] [FAILURE] [1] [AppException: null]");
+        assert containsLine(result.getOutput(), "FINISH [test unknownError(SomeTest)] [unknownError] [FAILURE] [1] [AppException]");
     }
 
     @Test
     public void canListenForTestResultsWhenJUnit3IsUsed() {
-        testDir.file('src/test/java/SomeTest.java').writelns(
+        testDirectory.file('src/test/java/SomeTest.java').writelns(
                 "public class SomeTest extends junit.framework.TestCase {",
                 "public void testPass() { }",
                 "public void testFail() { junit.framework.Assert.fail(\"message\"); }",
@@ -387,7 +378,7 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
                 "}"
         );
 
-        testDir.file('build.gradle') << '''
+        testDirectory.file('build.gradle') << '''
             apply plugin: 'java'
             repositories { mavenCentral() }
             dependencies { testCompile 'junit:junit:3.8' }
@@ -417,27 +408,37 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
     public void canHaveMultipleTestTaskInstances() {
         executer.withTasks('check').run()
 
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.Test1', 'org.gradle.Test2')
         result.testClass('org.gradle.Test1').assertTestPassed('ok')
         result.testClass('org.gradle.Test2').assertTestPassed('ok')
     }
 
-    // primarily tests that we don't crash like we used to
     @Test
     void canHandleMultipleThreadsWritingToSystemOut() {
         def result = executer.withTasks("test").run()
         assert result.getOutput().contains("thread 0 out")
         assert result.getOutput().contains("thread 1 out")
         assert result.getOutput().contains("thread 2 out")
+
+        def junitResult = new DefaultTestExecutionResult(testDirectory)
+        def testClass = junitResult.testClass("org.gradle.SystemOutTest")
+        testClass.assertStdout(containsText("thread 0 out"))
+        testClass.assertStdout(containsText("thread 1 out"))
+        testClass.assertStdout(containsText("thread 2 out"))
     }
 
-    // primarily tests that we don't crash like we used to
     @Test
     void canHandleMultipleThreadsWritingToSystemErr() {
         def result = executer.withTasks("test").run()
-        assert result.getOutput().contains("thread 0 out")
-        assert result.getOutput().contains("thread 1 out")
-        assert result.getOutput().contains("thread 2 out")
+        assert result.getOutput().contains("thread 0 err")
+        assert result.getOutput().contains("thread 1 err")
+        assert result.getOutput().contains("thread 2 err")
+
+        def junitResult = new DefaultTestExecutionResult(testDirectory)
+        def testClass = junitResult.testClass("org.gradle.SystemErrTest")
+        testClass.assertStderr(containsText("thread 0 err"))
+        testClass.assertStderr(containsText("thread 1 err"))
+        testClass.assertStderr(containsText("thread 2 err"))
     }
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitLoggingIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitLoggingIntegrationTest.groovy
index e2bfe6e..048f924 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitLoggingIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitLoggingIntegrationTest.groovy
@@ -17,10 +17,14 @@
 package org.gradle.testing.junit
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.TestResources
-import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.integtests.fixtures.executer.ExecutionResult
 import org.gradle.util.TextUtil
 import org.junit.Rule
+import org.junit.Test
+
+import static org.hamcrest.Matchers.equalTo
 
 // cannot make assumptions about order in which test methods of JUnit4Test get executed
 class JUnitLoggingIntegrationTest extends AbstractIntegrationSpec {
@@ -72,6 +76,46 @@ org.gradle.JUnit4StandardOutputTest > printTest STANDARD_OUT
         """)
     }
 
+    @Test
+    void "test logging is included in XML results"() {
+        file("build.gradle") << """
+            apply plugin: 'java'
+                repositories { mavenCentral() }
+                dependencies { testCompile 'junit:junit:4.11' }
+        """
+
+        file("src/test/java/EncodingTest.java") << """
+import org.junit.*;
+
+public class EncodingTest {
+    @Test public void encodesCdata() {
+        System.out.println("< html allowed, cdata closing token ]]> encoded!");
+        System.out.print("no EOL, ");
+        System.out.println("non-asci char: ż");
+        System.out.println("xml entity: &");
+        System.err.println("< html allowed, cdata closing token ]]> encoded!");
+    }
+    @Test public void encodesAttributeValues() {
+        throw new RuntimeException("html: <> cdata: ]]>");
+    }
+}
+"""
+        when:
+        executer.withTasks("test").runWithFailure()
+
+        then:
+        new DefaultTestExecutionResult(testDirectory)
+                .testClass("EncodingTest")
+                .assertTestPassed("encodesCdata")
+                .assertTestFailed("encodesAttributeValues", equalTo('java.lang.RuntimeException: html: <> cdata: ]]>'))
+                .assertStdout(equalTo("""< html allowed, cdata closing token ]]> encoded!
+no EOL, non-asci char: ż
+xml entity: &
+"""))
+                .assertStderr(equalTo("< html allowed, cdata closing token ]]> encoded!\n"))
+    }
+
+
     private void outputContains(String text) {
         assert result.output.contains(TextUtil.toPlatformLineSeparators(text.trim()))
     }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/SampleTestNGIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/SampleTestNGIntegrationTest.groovy
index 9f97603..ce6399e 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/SampleTestNGIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/SampleTestNGIntegrationTest.groovy
@@ -15,38 +15,37 @@
  */
 package org.gradle.testing.testng
 
+import org.gradle.integtests.fixtures.*
 import org.junit.Rule
 import org.junit.Test
-import org.gradle.integtests.fixtures.*
 
 /**
  * @author Tom Eyckmans
  */
-public class SampleTestNGIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+public class SampleTestNGIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public final Sample sample = new Sample()
 
-    @Test @UsesSample('testng/suitexmlbuilder')
+    @Test @UsesSample('testing/testng/suitexmlbuilder')
     public void suiteXmlBuilder() {
         executer.inDirectory(sample.dir).withTasks('clean', 'test').run()
 
-        def result = new TestNGExecutionResult(sample.dir)
+        def result = new DefaultTestExecutionResult(sample.dir)
         result.assertTestClassesExecuted('org.gradle.testng.UserImplTest')
         result.testClass('org.gradle.testng.UserImplTest').assertTestsExecuted('testOkFirstName')
         result.testClass('org.gradle.testng.UserImplTest').assertTestPassed('testOkFirstName')
     }
 
-    @Test @UsesSample('testng/java-jdk14-passing')
+    @Test @UsesSample('testing/testng/java-jdk14-passing')
     public void javaJdk14Passing() {
         executer.inDirectory(sample.dir).withTasks('clean', 'test').run()
 
-        def result = new TestNGExecutionResult(sample.dir)
+        def result = new DefaultTestExecutionResult(sample.dir)
         result.assertTestClassesExecuted('org.gradle.OkTest')
         result.testClass('org.gradle.OkTest').assertTestPassed('passingTest')
     }
     
-    @Test @UsesSample('testng/java-jdk15-passing')
+    @Test @UsesSample('testing/testng/java-jdk15-passing')
     public void javaJdk15Passing() {
         executer.inDirectory(sample.dir).withTasks('clean', 'test').run()
 
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy
index 063b851..e42336d 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy
@@ -15,11 +15,16 @@
  */
 package org.gradle.testing.testng
 
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.TestNGExecutionResult
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.executer.ExecutionResult
+import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
-import org.junit.Before
 import spock.lang.Issue
-import org.gradle.integtests.fixtures.*
+
 import static org.gradle.util.Matchers.containsLine
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertThat
@@ -27,9 +32,8 @@ import static org.junit.Assert.assertThat
 /**
  * @author Tom Eyckmans
  */
-class TestNGIntegrationTest {
-    @Rule public GradleDistribution dist = new GradleDistribution()
-    @Rule public GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class TestNGIntegrationTest extends AbstractIntegrationTest {
+
     @Rule public TestResources resources = new TestResources()
 
     @Before
@@ -45,7 +49,7 @@ class TestNGIntegrationTest {
         assertThat(result.error, not(containsString('stderr')))
         assertThat(result.error, not(containsString('a warning')))
 
-        new TestNGExecutionResult(dist.testDir).testClass('org.gradle.OkTest').assertTestPassed('ok')
+        new DefaultTestExecutionResult(testDirectory).testClass('org.gradle.OkTest').assertTestPassed('ok')
     }
 
     @Test
@@ -65,34 +69,34 @@ class TestNGIntegrationTest {
         assert containsLine(result.getOutput(), "START [test method knownError(SomeTest)] [knownError]");
         assert containsLine(result.getOutput(), "FINISH [test method knownError(SomeTest)] [knownError] [java.lang.RuntimeException: message]");
         assert containsLine(result.getOutput(), "START [test method unknownError(SomeTest)] [unknownError]");
-        assert containsLine(result.getOutput(), "FINISH [test method unknownError(SomeTest)] [unknownError] [AppException: null]");
+        assert containsLine(result.getOutput(), "FINISH [test method unknownError(SomeTest)] [unknownError] [AppException]");
     }
 
     @Test
     void groovyJdk15Failing() {
-        executer.withTasks("test").runWithFailure().assertThatCause(startsWith('There were failing tests'))
+        executer.withTasks("test").runWithFailure().assertTestsFailed()
 
-        def result = new TestNGExecutionResult(dist.testDir)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.BadTest')
-        result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('broken'))
+        result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('java.lang.IllegalArgumentException: broken'))
     }
 
     @Test
     void groovyJdk15Passing() {
         executer.withTasks("test").run()
 
-        def result = new TestNGExecutionResult(dist.testDir)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.OkTest')
         result.testClass('org.gradle.OkTest').assertTestPassed('passingTest')
     }
 
     @Test
     void javaJdk14Failing() {
-        executer.withTasks("test").runWithFailure().assertThatCause(startsWith('There were failing tests'))
+        executer.withTasks("test").runWithFailure().assertTestsFailed()
 
-        def result = new TestNGExecutionResult(dist.testDir)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.BadTest')
-        result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('broken'))
+        result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('java.lang.IllegalArgumentException: broken'))
     }
 
     @Issue("GRADLE-1822")
@@ -103,21 +107,25 @@ class TestNGIntegrationTest {
     }
 
     private void doJavaJdk15Failing(String testNGVersion) {
-        def execution = executer.withTasks("test").withArguments("-PtestNGVersion=$testNGVersion").runWithFailure().assertThatCause(startsWith('There were failing tests'))
+        executer.withTasks("test").withArguments("-PtestNGVersion=$testNGVersion").runWithFailure().assertTestsFailed()
 
-        def result = new TestNGExecutionResult(dist.testDir)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.BadTest', 'org.gradle.TestWithBrokenSetup', 'org.gradle.BrokenAfterSuite', 'org.gradle.TestWithBrokenMethodDependency')
-        result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('broken'))
-        result.testClass('org.gradle.TestWithBrokenSetup').assertConfigMethodFailed('setup')
-        result.testClass('org.gradle.BrokenAfterSuite').assertConfigMethodFailed('cleanup')
-        result.testClass('org.gradle.TestWithBrokenMethodDependency').assertTestFailed('broken', equalTo('broken'))
-        result.testClass('org.gradle.TestWithBrokenMethodDependency').assertTestSkipped('okTest')
+        result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('java.lang.IllegalArgumentException: broken'))
+        result.testClass('org.gradle.TestWithBrokenMethodDependency').assertTestFailed('broken', equalTo('java.lang.RuntimeException: broken'))
+        result.testClass('org.gradle.TestWithBrokenMethodDependency').assertTestsSkipped('okTest')
+
+        def ngResult = new TestNGExecutionResult(testDirectory)
+        ngResult.testClass('org.gradle.TestWithBrokenSetup').assertConfigMethodFailed('setup')
+        ngResult.testClass('org.gradle.BrokenAfterSuite').assertConfigMethodFailed('cleanup')
+        ngResult.testClass('org.gradle.TestWithBrokenMethodDependency').assertTestFailed('broken', equalTo('broken'))
+        ngResult.testClass('org.gradle.TestWithBrokenMethodDependency').assertTestSkipped('okTest')
     }
 
     @Issue("GRADLE-1532")
     @Test
     void supportsThreadPoolSize() {
-        dist.testDir.file('src/test/java/SomeTest.java') << """
+        testDirectory.file('src/test/java/SomeTest.java') << """
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -129,7 +137,7 @@ public class SomeTest {
 }
 """
 
-        dist.testDir.file("build.gradle") << """
+        testDirectory.file("build.gradle") << """
 apply plugin: "java"
 
 repositories {
@@ -152,7 +160,7 @@ test {
     @Test
     void supportsTestGroups() {
         executer.withTasks("test").run()
-        def result = new TestNGExecutionResult(dist.testDir)
+        def result = new DefaultTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.groups.SomeTest')
         result.testClass('org.gradle.groups.SomeTest').assertTestsExecuted("databaseTest")
     }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGLoggingIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGLoggingIntegrationTest.groovy
index f655fb9..286dfeb 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGLoggingIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGLoggingIntegrationTest.groovy
@@ -18,7 +18,7 @@ package org.gradle.testing.testng
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.TestResources
-import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.integtests.fixtures.executer.ExecutionResult
 import org.gradle.util.TextUtil
 import org.junit.Rule
 
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesJUnitXmlResultsIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesJUnitXmlResultsIntegrationTest.groovy
index edf6290..a5284a2 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesJUnitXmlResultsIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesJUnitXmlResultsIntegrationTest.groovy
@@ -18,11 +18,9 @@
 package org.gradle.testing.testng
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
-import spock.lang.Unroll
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 
 import static org.hamcrest.Matchers.*
-import static org.hamcrest.core.IsNot.not
 
 public class TestNGProducesJUnitXmlResultsIntegrationTest extends
         AbstractIntegrationSpec {
@@ -30,22 +28,23 @@ public class TestNGProducesJUnitXmlResultsIntegrationTest extends
         executer.allowExtraLogging = false
     }
 
-    @Unroll("#testConfiguration")
     def "produces JUnit xml results"() {
         expect:
-        assertProducesXmlResults(testConfiguration)
-
-        where:
-        testConfiguration << [
-                "useTestNG()",
-                "useTestNG(); forkEvery 1",
-                "useTestNG(); maxParallelForks 2"
-        ]
+        assertProducesXmlResults "useTestNG()"
+    }
+
+    def "produces JUnit xml results when running tests in parallel"() {
+        expect:
+        assertProducesXmlResults "useTestNG(); maxParallelForks 2"
+    }
+
+    def "produces JUnit xml results with aggressive forking"() {
+        expect:
+        assertProducesXmlResults "useTestNG(); forkEvery 1"
     }
 
     def assertProducesXmlResults(String testConfiguration) {
         file("src/test/java/org/MixedMethodsTest.java") << """package org;
-import org.testng.*;
 import org.testng.annotations.*;
 import static org.testng.Assert.*;
 
@@ -72,9 +71,7 @@ public class MixedMethodsTest {
 }
 """
         file("src/test/java/org/PassingTest.java") << """package org;
-import org.testng.*;
 import org.testng.annotations.*;
-import static org.testng.Assert.*;
 
 public class PassingTest {
     @Test public void passing() {
@@ -84,7 +81,6 @@ public class PassingTest {
 }
 """
         file("src/test/java/org/FailingTest.java") << """package org;
-import org.testng.*;
 import org.testng.annotations.*;
 import static org.testng.Assert.*;
 
@@ -99,9 +95,7 @@ public class FailingTest {
 }
 """
         file("src/test/java/org/NoOutputsTest.java") << """package org;
-import org.testng.*;
 import org.testng.annotations.*;
-import static org.testng.Assert.*;
 
 public class NoOutputsTest {
     @Test(enabled=false) public void skipped() {}
@@ -109,6 +103,23 @@ public class NoOutputsTest {
 }
 """
 
+        file("src/test/java/org/EncodingTest.java") << """package org;
+import org.testng.annotations.*;
+
+public class EncodingTest {
+    @Test public void encodesCdata() {
+        System.out.println("< html allowed, cdata closing token ]]> encoded!");
+        System.out.print("no EOL, ");
+        System.out.println("non-ascii char: ż");
+        System.out.println("xml entity: &");
+        System.err.println("< html allowed, cdata closing token ]]> encoded!");
+    }
+    @Test public void encodesAttributeValues() {
+        throw new RuntimeException("html: <> cdata: ]]> non-ascii: ż");
+    }
+}
+"""
+
         def buildFile = file('build.gradle')
         buildFile << """
 apply plugin: 'java'
@@ -116,17 +127,16 @@ repositories { mavenCentral() }
 dependencies { testCompile 'org.testng:testng:6.3.1' }
 
 test {
-    testReport = true
     $testConfiguration
 }
 """
         //when
-        executer.withTasks('test').runWithFailure()
+        executer.withTasks('test').runWithFailure().assertTestsFailed()
 
         //then
-        def junitResult = new JUnitTestExecutionResult(file("."));
+        def junitResult = new DefaultTestExecutionResult(file("."));
         junitResult
-            .assertTestClassesExecuted("org.FailingTest","org.PassingTest", "org.MixedMethodsTest", "org.NoOutputsTest")
+            .assertTestClassesExecuted("org.FailingTest","org.PassingTest", "org.MixedMethodsTest", "org.NoOutputsTest", "org.EncodingTest")
 
         junitResult.testClass("org.MixedMethodsTest")
             .assertTestCount(4, 2, 0)
@@ -160,5 +170,15 @@ test {
             .assertTestsExecuted("passing").assertTestPassed("passing")
             .assertStdout(equalTo(""))
             .assertStderr(equalTo(""))
+
+        junitResult.testClass("org.EncodingTest")
+            .assertTestCount(2, 1, 0)
+            .assertTestPassed("encodesCdata")
+            .assertTestFailed("encodesAttributeValues", equalTo('java.lang.RuntimeException: html: <> cdata: ]]> non-ascii: ż'))
+            .assertStdout(equalTo("""< html allowed, cdata closing token ]]> encoded!
+no EOL, non-ascii char: ż
+xml entity: &
+"""))
+            .assertStderr(equalTo("< html allowed, cdata closing token ]]> encoded!\n"))
     }
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesOldReportsIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesOldReportsIntegrationTest.groovy
index 33098d1..316102d 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesOldReportsIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesOldReportsIntegrationTest.groovy
@@ -20,7 +20,7 @@
 package org.gradle.testing.testng
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
 import org.gradle.integtests.fixtures.TestNGExecutionResult
 
 public class TestNGProducesOldReportsIntegrationTest extends AbstractIntegrationSpec {
@@ -28,7 +28,7 @@ public class TestNGProducesOldReportsIntegrationTest extends AbstractIntegration
         executer.allowExtraLogging = false
     }
 
-    def "produces only the old reports by default"() {
+    def "always produces the new xml reports"() {
         given:
         file("src/test/java/org/MixedMethodsTest.java") << """package org;
 import org.testng.*;
@@ -50,22 +50,19 @@ repositories { mavenCentral() }
 dependencies { testCompile 'org.testng:testng:6.3.1' }
 
 test {
+    testReport = false
     useTestNG()
 }
 """
         when:
-        executer.withTasks('test').runWithFailure()
+        executer.withTasks('test').runWithFailure().assertTestsFailed()
 
         then:
-        !new JUnitTestExecutionResult(file(".")).hasJUnitXmlResults()
-
-        def testNG = new TestNGExecutionResult(file("."))
-        testNG.hasTestNGXmlResults()
-        testNG.hasHtmlResults()
-        testNG.hasJUnitResultsGeneratedByTestNG()
+        !new TestNGExecutionResult(file(".")).hasTestNGXmlResults()
+        new DefaultTestExecutionResult(file(".")).hasJUnitXmlResults()
     }
 
-    def "can generate only the new reports"() {
+    def "can generate the old xml reports"() {
         given:
         file("src/test/java/org/SomeTest.java") << """package org;
 import org.testng.annotations.*;
@@ -80,40 +77,9 @@ apply plugin: 'java'
 repositories { mavenCentral() }
 dependencies { testCompile 'org.testng:testng:6.3.1' }
 test {
-  testReport = true
-  useTestNG()
-}
-"""
-        when:
-        executer.withTasks('test').run()
-
-        then:
-        new JUnitTestExecutionResult(file(".")).hasJUnitXmlResults()
-
-        def testNG = new TestNGExecutionResult(file("."))
-        !testNG.hasTestNGXmlResults()
-        !testNG.hasJUnitResultsGeneratedByTestNG()
-        testNG.hasHtmlResults()
-    }
-
-    def "can prevent generating the old and new reports"() {
-        given:
-        file("src/test/java/org/SomeTest.java") << """package org;
-import org.testng.annotations.*;
-
-public class SomeTest {
-    @Test public void passing() {}
-}
-"""
-        def buildFile = file('build.gradle')
-        buildFile << """
-apply plugin: 'java'
-repositories { mavenCentral() }
-dependencies { testCompile 'org.testng:testng:6.3.1' }
-test {
-  useTestNG {
-    useDefaultListeners = false
-    testReport = false
+  testReport = false
+  useTestNG(){
+    useDefaultListeners = true
   }
 }
 """
@@ -121,11 +87,11 @@ test {
         executer.withTasks('test').run()
 
         then:
-        !new JUnitTestExecutionResult(file(".")).hasJUnitXmlResults()
+        new DefaultTestExecutionResult(file(".")).hasJUnitXmlResults()
 
         def testNG = new TestNGExecutionResult(file("."))
-        !testNG.hasTestNGXmlResults()
-        !testNG.hasHtmlResults()
-        !testNG.hasJUnitResultsGeneratedByTestNG()
+        testNG.hasTestNGXmlResults()
+        testNG.hasJUnitResultsGeneratedByTestNG()
+        testNG.hasHtmlResults()
     }
 }
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canUseCustomFileExtensions/src/test/groovy/Person2.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canUseCustomFileExtensions/src/test/groovy/Person2.groovy
deleted file mode 100644
index ed11e7e..0000000
--- a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canUseCustomFileExtensions/src/test/groovy/Person2.groovy
+++ /dev/null
@@ -1,5 +0,0 @@
-class Person2 {
-    @org.junit.Test
-    void testMe() {}
-}
-
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canUseCustomFileExtensions/src/test/groovy/PersonTest.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canUseCustomFileExtensions/src/test/groovy/PersonTest.groovy
new file mode 100644
index 0000000..c96f935
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canUseCustomFileExtensions/src/test/groovy/PersonTest.groovy
@@ -0,0 +1,4 @@
+class PersonTest {
+    @org.junit.Test
+    void testMe() {}
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badCodeBreaksBuild/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badCodeBreaksBuild/build.gradle
deleted file mode 100644
index 2eda5b4..0000000
--- a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badCodeBreaksBuild/build.gradle
+++ /dev/null
@@ -1,9 +0,0 @@
-apply plugin: "groovy"
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    testCompile "junit:junit:4.10"
-}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badCodeBreaksBuild/src/main/groovy/BrokenClass.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badCodeBreaksBuild/src/main/groovy/BrokenClass.groovy
deleted file mode 100644
index 6e73e68..0000000
--- a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badCodeBreaksBuild/src/main/groovy/BrokenClass.groovy
+++ /dev/null
@@ -1,5 +0,0 @@
-
-class BrokenClass {
-    Unknown1 field1
-    Unknown2 field2
-}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badJavaCodeBreaksBuild/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badJavaCodeBreaksBuild/build.gradle
deleted file mode 100644
index 2eda5b4..0000000
--- a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badJavaCodeBreaksBuild/build.gradle
+++ /dev/null
@@ -1,9 +0,0 @@
-apply plugin: "groovy"
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    testCompile "junit:junit:4.10"
-}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canCompileAgainstGroovyClassThatDependsOnExternalClass/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canCompileAgainstGroovyClassThatDependsOnExternalClass/build.gradle
index 2eda5b4..daae5fa 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canCompileAgainstGroovyClassThatDependsOnExternalClass/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canCompileAgainstGroovyClassThatDependsOnExternalClass/build.gradle
@@ -5,5 +5,5 @@ repositories {
 }
 
 dependencies {
-    testCompile "junit:junit:4.10"
+    testCompile "junit:junit:4.11"
 }
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileBadCode/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileBadCode/build.gradle
new file mode 100644
index 0000000..c93ae49
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileBadCode/build.gradle
@@ -0,0 +1,5 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileBadCode/src/main/groovy/BrokenClass.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileBadCode/src/main/groovy/BrokenClass.groovy
new file mode 100644
index 0000000..5745292
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileBadCode/src/main/groovy/BrokenClass.groovy
@@ -0,0 +1,4 @@
+class BrokenClass {
+    Unknown1 field1
+    Unknown2 field2
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileBadJavaCode/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileBadJavaCode/build.gradle
new file mode 100644
index 0000000..c93ae49
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileBadJavaCode/build.gradle
@@ -0,0 +1,5 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badJavaCodeBreaksBuild/src/main/groovy/BrokenClass.java b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileBadJavaCode/src/main/groovy/BrokenClass.java
similarity index 100%
rename from subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badJavaCodeBreaksBuild/src/main/groovy/BrokenClass.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileBadJavaCode/src/main/groovy/BrokenClass.java
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badJavaCodeBreaksBuild/src/main/groovy/OkClass.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileBadJavaCode/src/main/groovy/OkClass.groovy
similarity index 100%
rename from subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badJavaCodeBreaksBuild/src/main/groovy/OkClass.groovy
rename to subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileBadJavaCode/src/main/groovy/OkClass.groovy
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileGoodCode/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileGoodCode/build.gradle
new file mode 100644
index 0000000..f09d7f9
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileGoodCode/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile "com.google.guava:guava:11.0.2"
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileGoodCode/src/main/groovy/Address.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileGoodCode/src/main/groovy/Address.groovy
new file mode 100644
index 0000000..424cbc9
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileGoodCode/src/main/groovy/Address.groovy
@@ -0,0 +1,9 @@
+import com.google.common.base.Strings
+
+class Address {
+    String street
+
+    String getNonNullStreet() {
+        Strings.nullToEmpty(street)
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileGoodCode/src/main/groovy/Person.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileGoodCode/src/main/groovy/Person.groovy
new file mode 100644
index 0000000..01ac9cc
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/compileGoodCode/src/main/groovy/Person.groovy
@@ -0,0 +1,9 @@
+class Person {
+    String name
+    int age
+    Address address
+
+    void sing() {
+        println "tra-la-la"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/javadoc/JavadocIntegrationTest/canCombineLocalOptionWithOtherOptions/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/javadoc/JavadocIntegrationTest/canCombineLocalOptionWithOtherOptions/build.gradle
new file mode 100644
index 0000000..23abe84
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/javadoc/JavadocIntegrationTest/canCombineLocalOptionWithOtherOptions/build.gradle
@@ -0,0 +1,21 @@
+import org.gradle.internal.jvm.Jvm
+
+apply plugin: "java"
+
+sourceSets {
+    taglet
+}
+
+dependencies {
+    tagletCompile files(Jvm.current().toolsJar)
+}
+
+javadoc {
+    dependsOn tagletClasses
+    options {
+        locale = 'de_DE'
+        breakIterator = true
+        taglets 'LocaleAwareTaglet'
+        tagletPath sourceSets.taglet.output.classesDir
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/javadoc/JavadocIntegrationTest/canCombineLocalOptionWithOtherOptions/src/main/java/Person.java b/subprojects/plugins/src/integTest/resources/org/gradle/javadoc/JavadocIntegrationTest/canCombineLocalOptionWithOtherOptions/src/main/java/Person.java
new file mode 100644
index 0000000..95564c6
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/javadoc/JavadocIntegrationTest/canCombineLocalOptionWithOtherOptions/src/main/java/Person.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Serial no. is valid javadoc!
+ *
+ * @LOCALE_AWARE
+ */
+public class Person {
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/javadoc/JavadocIntegrationTest/canCombineLocalOptionWithOtherOptions/src/taglet/java/LocaleAwareTaglet.java b/subprojects/plugins/src/integTest/resources/org/gradle/javadoc/JavadocIntegrationTest/canCombineLocalOptionWithOtherOptions/src/taglet/java/LocaleAwareTaglet.java
new file mode 100644
index 0000000..db2d5f6
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/javadoc/JavadocIntegrationTest/canCombineLocalOptionWithOtherOptions/src/taglet/java/LocaleAwareTaglet.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import com.sun.javadoc.Tag;
+import com.sun.tools.doclets.Taglet;
+
+import java.util.Locale;
+import java.util.Map;
+
+public class LocaleAwareTaglet implements Taglet {
+    public boolean inField() {
+        return false;
+    }
+
+    public boolean inConstructor() {
+        return false;
+    }
+
+    public boolean inMethod() {
+        return false;
+    }
+
+    public boolean inOverview() {
+        return true;
+    }
+
+    public boolean inPackage() {
+        return false;
+    }
+
+    public boolean inType() {
+        return true;
+    }
+
+    public boolean isInlineTag() {
+        return false;
+    }
+
+    public String getName() {
+        return "LOCALE_AWARE";
+    }
+
+    public String toString(Tag tag) {
+        return "<B>USED LOCALE=" + Locale.getDefault() + "</B>\n";
+    }
+
+    public String toString(Tag[] tags) {
+        return toString(tags[0]);
+    }
+
+    public static void register(Map tagletMap) {
+        LocaleAwareTaglet taglet = new LocaleAwareTaglet();
+        tagletMap.put(taglet.getName(), taglet);
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCrossVersionIntegrationSpec/canRunTestsUsingJUnit/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCrossVersionIntegrationSpec/canRunTestsUsingJUnit/build.gradle
new file mode 100644
index 0000000..d70026e
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitCrossVersionIntegrationSpec/canRunTestsUsingJUnit/build.gradle
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'java'
+
+repositories {
+    mavenCentral()
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemErr/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemErr/build.gradle
index 7358ac5..76284da 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemErr/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemErr/build.gradle
@@ -1,7 +1,7 @@
 apply plugin: 'groovy'
 repositories { mavenCentral() }
 dependencies { groovy localGroovy() }
-dependencies { testCompile 'junit:junit:4.8.2'}
+dependencies { testCompile 'junit:junit:4.11'}
 test {
     testLogging {
         showStandardStreams = true
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemErr/src/test/groovy/org/gradle/SystemErrTest.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemErr/src/test/groovy/org/gradle/SystemErrTest.groovy
index 2a93dc8..d48da1d 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemErr/src/test/groovy/org/gradle/SystemErrTest.groovy
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemErr/src/test/groovy/org/gradle/SystemErrTest.groovy
@@ -19,12 +19,12 @@ package org.gradle
 public class SystemErrTest {
     @org.junit.Test
     void test() {
-        System.err.println ("thread 0 out")
+        System.err.println ("thread 0 err")
         def thread1 = Thread.start {
-            System.err.println "thread 1 out"
+            System.err.println "thread 1 err"
         }
         def thread2 = Thread.start {
-            System.err.println "thread 2 out"
+            System.err.println "thread 2 err"
         }
         thread1.join()
         thread2.join()
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemOut/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemOut/build.gradle
index 7358ac5..76284da 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemOut/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemOut/build.gradle
@@ -1,7 +1,7 @@
 apply plugin: 'groovy'
 repositories { mavenCentral() }
 dependencies { groovy localGroovy() }
-dependencies { testCompile 'junit:junit:4.8.2'}
+dependencies { testCompile 'junit:junit:4.11'}
 test {
     testLogging {
         showStandardStreams = true
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHaveMultipleTestTaskInstances/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHaveMultipleTestTaskInstances/build.gradle
index f8f5d65..f2aaa6f 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHaveMultipleTestTaskInstances/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHaveMultipleTestTaskInstances/build.gradle
@@ -17,5 +17,5 @@ repositories {
 }
 
 dependencies {
-    testCompile 'junit:junit:4.8.2'
+    testCompile 'junit:junit:4.11'
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canRunSingleTests/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canRunSingleTests/build.gradle
index fd5d6b1..822260c 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canRunSingleTests/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canRunSingleTests/build.gradle
@@ -5,5 +5,5 @@ repositories {
 }
 
 dependencies {
-    testCompile 'junit:junit:4.8.2'
+    testCompile 'junit:junit:4.11'
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/detectsTestClasses/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/detectsTestClasses/build.gradle
index ed55229..5bf20b1 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/detectsTestClasses/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/detectsTestClasses/build.gradle
@@ -4,5 +4,5 @@ repositories {
     mavenCentral()
 }
 dependencies {
-    compile 'junit:junit:4.8.2'
+    compile 'junit:junit:4.11'
 }
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/build.gradle
index 25f7b6c..9baa5a2 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/build.gradle
@@ -1,6 +1,6 @@
 apply plugin: 'java'
 repositories { mavenCentral() }
-dependencies { testCompile 'junit:junit:4.8.2', 'ant:ant:1.6.1', 'ant:ant-launcher:1.6.1' }
+dependencies { testCompile 'junit:junit:4.11', 'ant:ant:1.6.1', 'ant:ant-launcher:1.6.1' }
 test {
     systemProperties.testSysProperty = 'value'
     systemProperties.projectDir = projectDir
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java
index f86dbe1..51d8bd7 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java
@@ -35,7 +35,7 @@ public class OkTest {
     @org.junit.Test
     public void ok() throws Exception {
         // check versions of dependencies
-        assertEquals("4.8.2", new org.junit.runner.JUnitCore().getVersion());
+        assertEquals("4.11", new org.junit.runner.JUnitCore().getVersion());
         assertTrue(org.apache.tools.ant.Main.getAntVersion().contains("1.6.1"));
 
         // check working dir
@@ -73,6 +73,7 @@ public class OkTest {
 
         // check stdout and stderr and logging
         System.out.println("This is test stdout");
+        System.out.println("non-asci char: ż");
         System.out.print("no EOL");
         System.out.println();
         System.err.println("This is test stderr");
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/build.gradle
index 554315f..7da1b0e 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/build.gradle
@@ -5,5 +5,5 @@ repositories {
 }
 
 dependencies {
-    testCompile "junit:junit:4.8.2"
+    testCompile "junit:junit:4.11"
 }
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/CustomIgnoredTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/CustomIgnoredTest.java
new file mode 100644
index 0000000..435cc7b
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/CustomIgnoredTest.java
@@ -0,0 +1,71 @@
+package org.gradle;
+
+import org.junit.Ignore;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runner.Runner;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunNotifier;
+
+import java.util.ArrayList;
+import java.util.List;
+
+ at Ignore
+ at RunWith(org.gradle.CustomIgnoredTest.TheRunner.class)
+public class CustomIgnoredTest {
+    static int count = 0;
+
+    public boolean doSomething() {
+        return true;
+    }
+
+    public static class TheRunner extends Runner {
+        List descriptions = new ArrayList();
+        private final Class<? extends org.gradle.CustomIgnoredTest> testClass;
+        private final org.gradle.CustomIgnoredTest testContainingInstance;
+        private Description testSuiteDescription;
+
+        public TheRunner(Class<? extends org.gradle.CustomIgnoredTest> testClass) {
+            this.testClass = testClass;
+            testContainingInstance = reflectMeATestContainingInstance(testClass);
+            testSuiteDescription = Description.createSuiteDescription("Custom Test with Suite ");
+            testSuiteDescription.addChild(createTestDescription("first test run"));
+            testSuiteDescription.addChild(createTestDescription("second test run"));
+            testSuiteDescription.addChild(createTestDescription("third test run"));
+        }
+
+        @Override
+        public Description getDescription() {
+            return testSuiteDescription;
+        }
+
+        @Override
+        public void run(RunNotifier notifier) {
+            for (Description description : testSuiteDescription.getChildren()) {
+                notifier.fireTestStarted(description);
+                try {
+                    if (testContainingInstance.doSomething()) {
+                        notifier.fireTestFinished(description);
+                    } else {
+                        notifier.fireTestIgnored(description);
+                    }
+                } catch (Exception e) {
+                    notifier.fireTestFailure(new Failure(description, e));
+                }
+            }
+
+        }
+
+        private org.gradle.CustomIgnoredTest reflectMeATestContainingInstance(Class<? extends org.gradle.CustomIgnoredTest> testClass) {
+            try {
+                return testClass.newInstance();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        private Description createTestDescription(String description) {
+            return Description.createTestDescription(testClass, description);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4_4Tests/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4_4Tests/build.gradle
deleted file mode 100644
index ac7b66a..0000000
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4_4Tests/build.gradle
+++ /dev/null
@@ -1,9 +0,0 @@
-apply plugin: 'java'
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    testCompile "junit:junit:4.4"
-}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/build.gradle
index a58531f..9d27852 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/build.gradle
@@ -1,3 +1,3 @@
 apply plugin: 'java'
 repositories { mavenCentral() }
-dependencies { testCompile 'junit:junit:4.8.2' }
+dependencies { testCompile 'junit:junit:4.11' }
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenTest.java
index 3abad56..76a09ac 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenTest.java
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenTest.java
@@ -12,6 +12,6 @@ public class BrokenTest {
 
     @Test
     public void broken() {
-        throw new IllegalStateException();
+        throw new IllegalStateException("html: <> cdata: ]]>");
     }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/CustomException.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/CustomException.java
new file mode 100644
index 0000000..001f6d5
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/CustomException.java
@@ -0,0 +1,17 @@
+package org.gradle;
+
+import org.junit.Test;
+
+public class CustomException {
+    @Test
+    public void custom() {
+        throw new CustomException.CustomRuntimeException();
+    }
+
+    private static class CustomRuntimeException extends RuntimeException {
+        @Override
+        public String toString() {
+            return "Exception with a custom toString implementation";
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/UnserializableException.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/UnserializableException.java
new file mode 100644
index 0000000..51ef669
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/UnserializableException.java
@@ -0,0 +1,24 @@
+package org.gradle;
+
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
+public class UnserializableException {
+
+    @Test
+    public void unserialized() {
+        throw new UnserializableRuntimeException("whatever", null);
+    }
+
+    static class UnserializableRuntimeException extends RuntimeException {
+        UnserializableRuntimeException(String message, Throwable cause) {
+            super(message, cause);
+        }
+
+        private void writeObject(ObjectOutputStream outstr) throws IOException {
+            outstr.writeObject(new Object());
+        }
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/build.gradle
index 36f2504..ea091ee 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/build.gradle
@@ -1,6 +1,6 @@
 apply plugin: 'java'
 repositories { mavenCentral() }
-dependencies { testCompile 'junit:junit:4.8.2'}
+dependencies { testCompile 'junit:junit:4.11'}
 test {
     include '**/ASuite.class'
     exclude '**/*Test.class'
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/src/test/java/org/gradle/ASuite.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/src/test/java/org/gradle/ASuite.java
index 29702fd..33c8e9b 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/src/test/java/org/gradle/ASuite.java
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/src/test/java/org/gradle/ASuite.java
@@ -28,6 +28,7 @@ public class ASuite {
 
     @org.junit.BeforeClass public static void init() {
         System.out.println("before suite class out");
+        System.out.println("non-asci char: ż");
         System.err.println("before suite class err");
     }
 
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/shared/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/shared/build.gradle
index 6a062b2..79280e7 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/shared/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/shared/build.gradle
@@ -5,8 +5,8 @@ repositories {
 }
 
 dependencies {
-    groovy "org.codehaus.groovy:groovy-all:1.8.8"
-    testCompile "junit:junit:4.10"
+    compile "org.codehaus.groovy:groovy-all:2.0.5"
+    testCompile "junit:junit:4.11"
 }
 
 test {
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/standardOutputLogging/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/standardOutputLogging/build.gradle
index 0dfc0d3..68c2ba1 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/standardOutputLogging/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/standardOutputLogging/build.gradle
@@ -21,8 +21,8 @@ repositories {
 }
 
 dependencies {
-    groovy "org.codehaus.groovy:groovy-all:1.8.8"
-    testCompile "junit:junit:4.10"
+    compile "org.codehaus.groovy:groovy-all:2.0.5"
+    testCompile "junit:junit:4.11"
 }
 
 test {
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Failing/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Failing/build.gradle
index b23bc4b..0e7278c 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Failing/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Failing/build.gradle
@@ -7,7 +7,7 @@ repositories {
 }
 
 dependencies {
-	groovy "org.codehaus.groovy:groovy-all:1.8.8"
+	compile "org.codehaus.groovy:groovy-all:2.0.5"
     testCompile 'org.testng:testng:6.3.1'
 }
 
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Passing/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Passing/build.gradle
index b23bc4b..0e7278c 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Passing/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Passing/build.gradle
@@ -7,7 +7,7 @@ repositories {
 }
 
 dependencies {
-	groovy "org.codehaus.groovy:groovy-all:1.8.8"
+	compile "org.codehaus.groovy:groovy-all:2.0.5"
     testCompile 'org.testng:testng:6.3.1'
 }
 
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk15Failing/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk15Failing/build.gradle
index 0d80e2b..7293378 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk15Failing/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk15Failing/build.gradle
@@ -1,6 +1,6 @@
 apply plugin: 'java'
 
-sourceCompatibility=1.5
+sourceCompatibility = 1.5
 
 repositories {
     mavenCentral()
@@ -11,5 +11,7 @@ dependencies {
 }
 
 test {
-   useTestNG()
+    useTestNG() {
+        useDefaultListeners = true
+    }
 }
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/shared/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/shared/build.gradle
index c1bd8bb..fcabda8 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/shared/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/shared/build.gradle
@@ -5,7 +5,7 @@ repositories {
 }
 
 dependencies {
-    groovy "org.codehaus.groovy:groovy-all:1.8.8"
+    compile "org.codehaus.groovy:groovy-all:2.0.5"
     testCompile "org.testng:testng:6.3.1"
 }
 
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/standardOutputLogging/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/standardOutputLogging/build.gradle
index d2465c6..c104119 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/standardOutputLogging/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/standardOutputLogging/build.gradle
@@ -21,7 +21,7 @@ repositories {
 }
 
 dependencies {
-    groovy "org.codehaus.groovy:groovy-all:1.8.8"
+    compile "org.codehaus.groovy:groovy-all:2.0.5"
     testCompile "org.testng:testng:6.3.1"
 }
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/java/JavaLibrary.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/java/JavaLibrary.java
new file mode 100644
index 0000000..e4544a6
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/java/JavaLibrary.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.java;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.DependencySet;
+import org.gradle.api.artifacts.PublishArtifactSet;
+import org.gradle.api.internal.component.SoftwareComponentInternal;
+
+/**
+ * A SoftwareComponent representing a library that runs on a java virtual machine.
+ */
+public class JavaLibrary implements SoftwareComponentInternal {
+
+    private final Configuration runtimeConfiguration;
+
+    public JavaLibrary(Configuration runtimeConfiguration) {
+        this.runtimeConfiguration = runtimeConfiguration;
+    }
+
+    public String getName() {
+        return "java";
+    }
+
+    public PublishArtifactSet getArtifacts() {
+        return runtimeConfiguration.getAllArtifacts();
+    }
+
+    public DependencySet getRuntimeDependencies() {
+        return runtimeConfiguration.getAllDependencies();
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/GroovyJarFile.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/GroovyJarFile.java
new file mode 100644
index 0000000..735fb76
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/GroovyJarFile.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.plugins;
+
+import org.gradle.api.Nullable;
+import org.gradle.util.VersionNumber;
+
+import java.io.File;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class GroovyJarFile {
+    private static final Pattern FILE_NAME_PATTERN = Pattern.compile("(groovy(?:-all)?)-(\\d.*?)(-indy)?.jar");
+
+    private final File file;
+    private final Matcher matcher;
+
+    private GroovyJarFile(File file, Matcher matcher) {
+        this.file = file;
+        this.matcher = matcher;
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public String getBaseName() {
+        return matcher.group(1);
+    }
+
+    public VersionNumber getVersion() {
+        return VersionNumber.parse(matcher.group(2));
+    }
+
+    public boolean isGroovyAll() {
+        return getBaseName().equals("groovy-all");
+    }
+
+    public boolean isIndy() {
+        return matcher.group(3) != null;
+    }
+
+    public String getDependencyNotation() {
+        String result = "org.codehaus.groovy:" + getBaseName() + ":" + getVersion();
+        if (isIndy()) {
+            result += ":indy";
+        }
+        return result;
+    }
+
+    @Nullable
+    public static GroovyJarFile parse(File file) {
+        Matcher matcher = FILE_NAME_PATTERN.matcher(file.getName());
+        return matcher.matches() ? new GroovyJarFile(file, matcher) : null;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/StartScriptGenerator.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/StartScriptGenerator.groovy
index 97ff114..be5601f 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/StartScriptGenerator.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/StartScriptGenerator.groovy
@@ -19,6 +19,7 @@ import groovy.text.SimpleTemplateEngine
 import org.gradle.util.TextUtil
 import org.gradle.util.AntUtil
 import org.apache.tools.ant.taskdefs.Chmod
+import org.gradle.util.GFileUtils
 
 class StartScriptGenerator {
     /**
@@ -100,7 +101,7 @@ class StartScriptGenerator {
     }
 
     void writeToFile(String scriptContent, File scriptFile) {
-        scriptFile.parentFile.mkdirs()
+        GFileUtils.mkdirs(scriptFile.parentFile)
         scriptFile.write(scriptContent)
     }
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DecoratingTestDescriptor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DecoratingTestDescriptor.java
index e06bf88..b580d47 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DecoratingTestDescriptor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DecoratingTestDescriptor.java
@@ -41,7 +41,7 @@ public class DecoratingTestDescriptor implements TestDescriptorInternal {
     }
 
     public String getClassName() {
-        return descriptor.getClassName();
+        return findClassName();
     }
 
     public String getName() {
@@ -51,4 +51,14 @@ public class DecoratingTestDescriptor implements TestDescriptorInternal {
     public boolean isComposite() {
         return descriptor.isComposite();
     }
+
+    private String findClassName() {
+        String className;
+        if (parent != null && parent.getName().equals(parent.getClassName())) {
+            className = parent.getName();
+        } else {
+            className = descriptor.getClassName();
+        }
+        return className;
+    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestFramework.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestFramework.java
index 89dcbe1..6074f56 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestFramework.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestFramework.java
@@ -32,8 +32,6 @@ public interface TestFramework {
      */
     TestFrameworkDetector getDetector();
 
-    void report();
-
     TestFrameworkOptions getOptions();
 
     /**
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/AbstractTestFrameworkDetector.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/AbstractTestFrameworkDetector.java
index 14cf28c..ba6589b 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/AbstractTestFrameworkDetector.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/AbstractTestFrameworkDetector.java
@@ -48,6 +48,7 @@ public abstract class AbstractTestFrameworkDetector<T extends TestClassVisitor>
     private FileCollection testClasspath;
 
     protected AbstractTestFrameworkDetector(ClassFileExtractionManager classFileExtractionManager) {
+        assert classFileExtractionManager != null;
         this.classFileExtractionManager = classFileExtractionManager;
         this.superClasses = new HashMap<File, Boolean>();
         this.knownTestCaseClassNames = new ArrayList<String>();
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java
index 0808634..2088cd8 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java
@@ -41,6 +41,7 @@ public class ClassFileExtractionManager {
     private final TemporaryFileProvider tempDirProvider;
 
     public ClassFileExtractionManager(final Factory<File> tempDirFactory) {
+        assert tempDirFactory != null;
         tempDirProvider = new DefaultTemporaryFileProvider(tempDirFactory);
         packageJarFilesMappings = new HashMap<String, Set<File>>();
         extractedJarClasses = new HashMap<String, File>();
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/AllExceptIgnoredTestRunnerBuilder.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/AllExceptIgnoredTestRunnerBuilder.java
new file mode 100644
index 0000000..50becdf
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/AllExceptIgnoredTestRunnerBuilder.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit;
+
+import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
+import org.junit.internal.builders.IgnoredBuilder;
+import org.junit.internal.builders.JUnit4Builder;
+import org.junit.runner.Runner;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Constructor;
+
+public class AllExceptIgnoredTestRunnerBuilder extends AllDefaultPossibilitiesBuilder {
+
+    public AllExceptIgnoredTestRunnerBuilder() {
+        super(true);
+    }
+
+    @Override
+    protected IgnoredBuilder ignoredBuilder() {
+        return new IgnoredIgnoredBuilder();
+    }
+
+    protected JUnit4Builder junit4Builder() {
+        return new FallbackJUnit4Builder();
+    }
+
+    private class FallbackJUnit4Builder extends JUnit4Builder {
+        public Runner runnerForClass(Class<?> testClass) throws Throwable {
+            try {
+                return new BlockJUnit4ClassRunner(testClass);
+            } catch (Throwable t) {
+                //failed to instantiate BlockJUnitRunner. try deprecated JUnitRunner (for JUnit < 4.5)
+                try {
+                    Class<Runner> runnerClass = (Class<Runner>) Thread.currentThread().getContextClassLoader().loadClass("org.junit.internal.runners.JUnit4ClassRunner");
+                    final Constructor<Runner> constructor = runnerClass.getConstructor(Class.class);
+                    return constructor.newInstance(testClass);
+                } catch (Throwable e) {
+                    LoggerFactory.getLogger(getClass()).warn("Unable to load JUnit4 runner to calculate Ignored test cases", e);
+                }
+            }
+            return null;
+        }
+    }
+
+    private class IgnoredIgnoredBuilder extends IgnoredBuilder {
+        @Override
+        public Runner runnerForClass(Class<?> testClass) {
+            return null;
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java
index c8c2d83..1ff8140 100755
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java
@@ -24,7 +24,6 @@ import org.gradle.api.internal.tasks.testing.results.AttachParentTestResultProce
 import org.gradle.internal.TimeProvider;
 import org.gradle.internal.TrueTimeProvider;
 import org.gradle.internal.id.IdGenerator;
-import org.gradle.listener.ListenerBroadcast;
 import org.gradle.logging.StandardOutputRedirector;
 import org.gradle.messaging.actor.Actor;
 import org.gradle.messaging.actor.ActorFactory;
@@ -54,10 +53,7 @@ public class JUnitTestClassProcessor implements TestClassProcessor {
     public void startProcessing(TestResultProcessor resultProcessor) {
         // Build a result processor chain
         ClassLoader applicationClassLoader = Thread.currentThread().getContextClassLoader();
-        ListenerBroadcast<TestResultProcessor> processors = new ListenerBroadcast<TestResultProcessor>(TestResultProcessor.class);
-        processors.add(new JUnitXmlReportGenerator(testResultsDir));
-        processors.add(resultProcessor);
-        TestResultProcessor resultProcessorChain = new AttachParentTestResultProcessor(new CaptureTestOutputTestResultProcessor(processors.getSource(), outputRedirector));
+        TestResultProcessor resultProcessorChain = new AttachParentTestResultProcessor(new CaptureTestOutputTestResultProcessor(resultProcessor, outputRedirector));
         TestClassExecutionEventGenerator eventGenerator = new TestClassExecutionEventGenerator(resultProcessorChain, idGenerator, timeProvider);
 
         // Wrap the result processor chain up in a blocking actor, to make the whole thing thread-safe
@@ -71,7 +67,7 @@ public class JUnitTestClassProcessor implements TestClassProcessor {
     }
 
     public void processTestClass(TestClassRunInfo testClass) {
-        LOGGER.debug("Executing test {}", testClass.getTestClassName());
+        LOGGER.debug("Executing test class {}", testClass.getTestClassName());
         executer.execute(testClass.getTestClassName());
     }
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java
index 6f639b4..7b1535c 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java
@@ -22,8 +22,11 @@ import org.gradle.internal.TimeProvider;
 import org.gradle.internal.concurrent.ThreadSafe;
 import org.gradle.internal.id.IdGenerator;
 import org.junit.runner.Description;
+import org.junit.runner.Request;
+import org.junit.runner.Runner;
 import org.junit.runner.notification.Failure;
 import org.junit.runner.notification.RunListener;
+import org.slf4j.LoggerFactory;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -77,14 +80,36 @@ public class JUnitTestEventAdapter extends RunListener {
     @Override
     public void testIgnored(Description description) throws Exception {
         if (methodName(description) == null) {
-            // An @Ignored class, ignore the event. We don't get testIgnored events for each method, which would be kind of nice
+            // An @Ignored class, ignore the event. We don't get testIgnored events for each method, so we have
+            // generate them on our own
+            processIgnoredClass(description);
             return;
         }
+
         TestDescriptorInternal testInternal = descriptor(idGenerator.generateId(), description);
         resultProcessor.started(testInternal, startEvent());
         resultProcessor.completed(testInternal.getId(), new TestCompleteEvent(timeProvider.getCurrentTime(), TestResult.ResultType.SKIPPED));
     }
 
+    private void processIgnoredClass(Description description) throws Exception {
+        String className = className(description);
+        final AllExceptIgnoredTestRunnerBuilder allExceptIgnoredTestRunnerBuilder = new AllExceptIgnoredTestRunnerBuilder();
+        try {
+            final Class<?> testClass = description.getClass().getClassLoader().loadClass(className);
+            Runner runner = allExceptIgnoredTestRunnerBuilder.runnerForClass(testClass);
+            if (runner == null) {
+                //fall back to default runner
+                runner = Request.aClass(testClass).getRunner();
+            }
+            final Description runnerDescription = runner.getDescription();
+            for (Description childrenDescription : runnerDescription.getChildren()) {
+                testIgnored(childrenDescription);
+            }
+        } catch (Throwable throwable) {
+            LoggerFactory.getLogger(getClass()).warn("Unable to process IgnoredClass", throwable);
+        }
+    }
+
     @Override
     public void testFinished(Description description) throws Exception {
         long endTime = timeProvider.getCurrentTime();
@@ -133,7 +158,7 @@ public class JUnitTestEventAdapter extends RunListener {
     }
 
     private Matcher methodStringMatcher(Description description) {
-        return Pattern.compile("(.*)\\((.*)\\)").matcher(description.toString());
+        return Pattern.compile("(.*)\\((.*)\\)", Pattern.DOTALL).matcher(description.toString());
     }
 
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFramework.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFramework.java
index b14e896..e2dbdda 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFramework.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFramework.java
@@ -21,8 +21,6 @@ import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 import org.gradle.api.internal.tasks.testing.TestFramework;
 import org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory;
 import org.gradle.api.internal.tasks.testing.detection.ClassFileExtractionManager;
-import org.gradle.api.internal.tasks.testing.junit.report.DefaultTestReport;
-import org.gradle.api.internal.tasks.testing.junit.report.TestReporter;
 import org.gradle.api.tasks.testing.Test;
 import org.gradle.api.tasks.testing.junit.JUnitOptions;
 import org.gradle.internal.id.IdGenerator;
@@ -37,14 +35,12 @@ import java.io.Serializable;
  * @author Tom Eyckmans
  */
 public class JUnitTestFramework implements TestFramework {
-    private TestReporter reporter;
     private JUnitOptions options;
     private JUnitDetector detector;
     private final Test testTask;
 
     public JUnitTestFramework(Test testTask) {
         this.testTask = testTask;
-        reporter = new DefaultTestReport();
         options = new JUnitOptions();
         detector = new JUnitDetector(new ClassFileExtractionManager(testTask.getTemporaryDirFactory()));
     }
@@ -64,15 +60,6 @@ public class JUnitTestFramework implements TestFramework {
         };
     }
 
-    public void report() {
-        if (!testTask.isTestReport()) {
-            return;
-        }
-        reporter.setTestReportDir(testTask.getTestReportDir());
-        reporter.setTestResultsDir(testTask.getTestResultsDir());
-        reporter.generateReport();
-    }
-
     public JUnitOptions getOptions() {
         return options;
     }
@@ -81,14 +68,6 @@ public class JUnitTestFramework implements TestFramework {
         this.options = options;
     }
 
-    TestReporter getReporter() {
-        return reporter;
-    }
-
-    void setReporter(TestReporter reporter) {
-        this.reporter = reporter;
-    }
-
     public JUnitDetector getDetector() {
         return detector;
     }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitXmlReportGenerator.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitXmlReportGenerator.java
deleted file mode 100644
index f2da74f..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitXmlReportGenerator.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.tasks.testing.junit;
-
-import org.gradle.api.internal.tasks.testing.TestDescriptorInternal;
-import org.gradle.api.internal.tasks.testing.junit.result.XmlTestSuiteFactory;
-import org.gradle.api.internal.tasks.testing.junit.result.XmlTestSuite;
-import org.gradle.api.internal.tasks.testing.results.StateTrackingTestResultProcessor;
-import org.gradle.api.internal.tasks.testing.results.TestState;
-import org.gradle.api.tasks.testing.TestDescriptor;
-import org.gradle.api.tasks.testing.TestOutputEvent;
-
-import java.io.File;
-
-public class JUnitXmlReportGenerator extends StateTrackingTestResultProcessor {
-    private final File testResultsDir;
-    private final XmlTestSuiteFactory testSuiteFactory = new XmlTestSuiteFactory();
-    private TestState testSuite;
-    private XmlTestSuite xmlTestSuite;
-
-    public JUnitXmlReportGenerator(File testResultsDir) {
-        this.testResultsDir = testResultsDir;
-    }
-
-    public void output(TestDescriptor test, TestOutputEvent event) {
-        xmlTestSuite.addOutput(event.getDestination(), event.getMessage());
-    }
-
-    @Override
-    protected void started(TestState state) {
-        TestDescriptorInternal test = state.test;
-        if (test.getName().equals(test.getClassName())) {
-            xmlTestSuite = testSuiteFactory.create(testResultsDir, test.getClassName(), state.getStartTime());
-            testSuite = state;
-        }
-    }
-
-    @Override
-    protected void completed(TestState state) {
-        if (!state.equals(testSuite)) {
-            xmlTestSuite.addTestCase(state.test.getName(), state.resultType, state.getExecutionTime(), state.failures);
-        } else {
-            xmlTestSuite.writeSuiteData(state.getExecutionTime());
-            testSuite = null;
-        }
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/TestNGJUnitXmlReportGenerator.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/TestNGJUnitXmlReportGenerator.java
deleted file mode 100644
index 41a7e68..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/TestNGJUnitXmlReportGenerator.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.tasks.testing.junit;
-
-import org.gradle.api.internal.tasks.testing.TestCompleteEvent;
-import org.gradle.api.internal.tasks.testing.TestDescriptorInternal;
-import org.gradle.api.internal.tasks.testing.TestResultProcessor;
-import org.gradle.api.internal.tasks.testing.TestStartEvent;
-import org.gradle.api.internal.tasks.testing.junit.result.XmlTestSuite;
-import org.gradle.api.internal.tasks.testing.junit.result.XmlTestSuiteFactory;
-import org.gradle.api.tasks.testing.TestOutputEvent;
-import org.gradle.internal.TimeProvider;
-import org.gradle.internal.TrueTimeProvider;
-
-import java.io.File;
-import java.util.*;
-
-public class TestNGJUnitXmlReportGenerator implements TestResultProcessor {
-
-    //TODO SF there's still duplication between JUnit / TestNG processors wrt generation of the xml results.
-    //this one probably deserves some unit testing
-
-    private final File testResultsDir;
-    private final XmlTestSuiteFactory testSuiteFactory = new XmlTestSuiteFactory();
-
-    private Map<Object, TestInfo> tests = new HashMap<Object, TestInfo>();
-    private Map<String, XmlTestSuite> testSuites = new HashMap<String, XmlTestSuite>();
-    private Map<Object, Collection<Throwable>> failures = new HashMap<Object, Collection<Throwable>>();
-
-    private TimeProvider timeProvider = new TrueTimeProvider();
-
-    public TestNGJUnitXmlReportGenerator(File testResultsDir) {
-        this.testResultsDir = testResultsDir;
-    }
-
-    class TestInfo {
-        TestDescriptorInternal test;
-        long started;
-        TestInfo(TestDescriptorInternal test, long started) {
-            this.test = test;
-            this.started = started;
-        }
-    }
-
-    public void started(TestDescriptorInternal test, TestStartEvent event) {
-        //it would be nice if we didn't have to maintain the testId->descriptor map
-        tests.put(test.getId(), new TestInfo(test, event.getStartTime()));
-        if (!test.isComposite()) { //test method
-            if (!testSuites.containsKey(test.getClassName())) {
-                testSuites.put(test.getClassName(), testSuiteFactory.create(testResultsDir, test.getClassName(), timeProvider.getCurrentTime()));
-            }
-        }
-    }
-
-    public void completed(final Object testId, final TestCompleteEvent event) {
-        final TestInfo testInfo = tests.remove(testId);
-        if (!testInfo.test.isComposite()) { //test method
-            XmlTestSuite xmlTestSuite = testSuites.get(testInfo.test.getClassName());
-            Collection<Throwable> failures = this.failures.containsKey(testId) ? this.failures.remove(testId) : Collections.<Throwable>emptySet();
-            xmlTestSuite.addTestCase(testInfo.test.getName(), event.getResultType(), event.getEndTime() - testInfo.started, failures);
-        } else if (testInfo.test.getParent() == null) {
-            for (XmlTestSuite xmlTestSuite : testSuites.values()) {
-                xmlTestSuite.writeSuiteData(0); //it's hard to reliably say when TestNG test class has finished.
-            }
-            testSuites.clear();
-        }
-    }
-
-    public void output(Object testId, TestOutputEvent event) {
-        TestInfo testInfo = tests.get(testId);
-        if (testInfo != null && testSuites.containsKey(testInfo.test.getClassName())) {
-            //if the test does not exist or suite it means it was already completed.
-            // TODO SF we should add this output to the parent class but in case of TestNG, atm we dont know what the parent class is
-            // because the start/end class events are not generated.
-            testSuites.get(testInfo.test.getClassName()).addOutput(event.getDestination(), event.getMessage());
-        }
-    }
-
-    public void failure(Object testId, Throwable failure) {
-        if (!failures.containsKey(testId)) {
-            failures.put(testId, new LinkedHashSet<Throwable>());
-        }
-        failures.get(testId).add(failure);
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassPageRenderer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassPageRenderer.java
index ba9dcbd..83d8ec2 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassPageRenderer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassPageRenderer.java
@@ -16,13 +16,26 @@
 package org.gradle.api.internal.tasks.testing.junit.report;
 
 import org.gradle.api.Action;
+import org.gradle.api.internal.tasks.testing.junit.result.TestResultsProvider;
+import org.gradle.api.tasks.testing.TestOutputEvent;
 import org.gradle.reporting.CodePanelRenderer;
 import org.w3c.dom.Element;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
 class ClassPageRenderer extends PageRenderer<ClassTestResults> {
     private final CodePanelRenderer codePanelRenderer = new CodePanelRenderer();
+    private final TestResultsProvider resultsProvider;
+    private final String className;
+
+    public ClassPageRenderer(String className, TestResultsProvider provider) {
+        this.className = className;
+        this.resultsProvider = provider;
+    }
 
-    @Override protected void renderBreadcrumbs(Element parent) {
+    @Override
+    protected void renderBreadcrumbs(Element parent) {
         Element div = append(parent, "div");
         div.setAttribute("class", "breadcrumbs");
         appendLink(div, "index.html", "all");
@@ -60,12 +73,8 @@ class ClassPageRenderer extends PageRenderer<ClassTestResults> {
         }
     }
 
-    private void renderStdOut(Element parent) {
-        codePanelRenderer.render(getResults().getStandardOutput().toString(), parent);
-    }
-
-    private void renderStdErr(Element parent) {
-        codePanelRenderer.render(getResults().getStandardError().toString(), parent);
+    private void renderStd(Element parent, String stdString) {
+        codePanelRenderer.render(stdString, parent);
     }
 
     @Override protected void registerTabs() {
@@ -75,19 +84,32 @@ class ClassPageRenderer extends PageRenderer<ClassTestResults> {
                 renderTests(element);
             }
         });
-        if (getResults().getStandardOutput().length() > 0) {
+        final String stdOut = getOutputString(TestOutputEvent.Destination.StdOut);
+        if (stdOut.length() > 0) {
             addTab("Standard output", new Action<Element>() {
                 public void execute(Element element) {
-                    renderStdOut(element);
+                    renderStd(element, stdOut);
                 }
             });
         }
-        if (getResults().getStandardError().length() > 0) {
+        final String stdErr = getOutputString(TestOutputEvent.Destination.StdErr);
+        if (stdErr.length() > 0) {
             addTab("Standard error", new Action<Element>() {
                 public void execute(Element element) {
-                    renderStdErr(element);
+                    renderStd(element, stdErr);
                 }
             });
         }
     }
+
+    /**
+     * @TODO RG: This method can consume a lot of memory depending on the amount of output We'll when moving away from dom based report generation
+     */
+    private String getOutputString(TestOutputEvent.Destination destination) {
+        final StringWriter stringWriter = new StringWriter();
+        PrintWriter writer = new PrintWriter(stringWriter);
+        resultsProvider.writeOutputs(className, destination, writer);
+        writer.close();
+        return stringWriter.toString();
+    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassTestResults.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassTestResults.java
index a91aebf..4dc2e2a 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassTestResults.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassTestResults.java
@@ -28,8 +28,6 @@ public class ClassTestResults extends CompositeTestResults {
     private final String name;
     private final PackageTestResults packageResults;
     private final Set<TestResult> results = new TreeSet<TestResult>();
-    private final StringBuilder standardOutput = new StringBuilder();
-    private final StringBuilder standardError = new StringBuilder();
 
     public ClassTestResults(String name, PackageTestResults packageResults) {
         super(packageResults);
@@ -62,25 +60,9 @@ public class ClassTestResults extends CompositeTestResults {
         return results;
     }
 
-    public CharSequence getStandardError() {
-        return standardError;
-    }
-
-    public CharSequence getStandardOutput() {
-        return standardOutput;
-    }
-
     public TestResult addTest(String testName, long duration) {
         TestResult test = new TestResult(testName, duration, this);
         results.add(test);
         return addTest(test);
     }
-
-    public void addStandardOutput(String textContent) {
-        standardOutput.append(textContent);
-    }
-
-    public void addStandardError(String textContent) {
-        standardError.append(textContent);
-    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReport.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReport.java
index e67f325..918cb86 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReport.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReport.java
@@ -15,23 +15,24 @@
  */
 package org.gradle.api.internal.tasks.testing.junit.report;
 
+import org.gradle.api.Action;
 import org.gradle.api.GradleException;
+import org.gradle.api.internal.tasks.testing.junit.result.TestClassResult;
+import org.gradle.api.internal.tasks.testing.junit.result.TestMethodResult;
+import org.gradle.api.internal.tasks.testing.junit.result.TestResultsProvider;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
 import org.gradle.reporting.HtmlReportRenderer;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
-import org.xml.sax.InputSource;
+import org.gradle.util.Clock;
 
-import javax.xml.parsers.DocumentBuilderFactory;
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.math.BigDecimal;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
 
 public class DefaultTestReport implements TestReporter {
     private final HtmlReportRenderer htmlRenderer = new HtmlReportRenderer();
-    private File resultDir;
-    private File reportDir;
+    private final static Logger LOG = Logging.getLogger(DefaultTestReport.class);
 
     public DefaultTestReport() {
         htmlRenderer.requireResource(getClass().getResource("/org/gradle/reporting/report.js"));
@@ -40,84 +41,59 @@ public class DefaultTestReport implements TestReporter {
         htmlRenderer.requireResource(getClass().getResource("style.css"));
     }
 
-    public void setTestResultsDir(File resultDir) {
-        this.resultDir = resultDir;
-    }
-
-    public void setTestReportDir(File reportDir) {
-        this.reportDir = reportDir;
-    }
+    public void generateReport(TestResultsProvider resultsProvider, File reportDir) {
+        LOG.info("Generating HTML test report...");
 
-    public void generateReport() {
-        AllTestResults model = loadModel();
-        generateFiles(model);
+        Clock clock = new Clock();
+        AllTestResults model = loadModelFromProvider(resultsProvider);
+        generateFiles(model, resultsProvider, reportDir);
+        LOG.info("Finished generating test html results (" + clock.getTime() + ")");
     }
 
-    private AllTestResults loadModel() {
-        AllTestResults model = new AllTestResults();
-        if (resultDir.exists()) {
-            for (File file : resultDir.listFiles()) {
-                if (file.getName().startsWith("TEST-") && file.getName().endsWith(".xml")) {
-                    mergeFromFile(file, model);
+    private AllTestResults loadModelFromProvider(TestResultsProvider resultsProvider) {
+        final AllTestResults model = new AllTestResults();
+        resultsProvider.visitClasses(new Action<TestClassResult>() {
+            public void execute(TestClassResult classResult) {
+                List<TestMethodResult> collectedResults = classResult.getResults();
+                for (TestMethodResult collectedResult : collectedResults) {
+                    final TestResult testResult = model.addTest(classResult.getClassName(), collectedResult.getName(), collectedResult.getDuration());
+                    if (collectedResult.getResultType() == org.gradle.api.tasks.testing.TestResult.ResultType.SKIPPED) {
+                        testResult.ignored();
+                    } else {
+                        List<Throwable> failures = collectedResult.getExceptions();
+                        for (Throwable throwable : failures) {
+                            testResult.addFailure(throwable.getMessage(), stackTrace(throwable));
+                        }
+                    }
                 }
             }
-        }
+        });
         return model;
     }
 
-    private void mergeFromFile(File file, AllTestResults model) {
+    private String stackTrace(Throwable throwable) {
         try {
-            InputStream inputStream = new FileInputStream(file);
-            Document document;
-            try {
-                document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(inputStream));
-            } finally {
-                inputStream.close();
-            }
-            NodeList testCases = document.getElementsByTagName("testcase");
-            for (int i = 0; i < testCases.getLength(); i++) {
-                Element testCase = (Element) testCases.item(i);
-                String className = testCase.getAttribute("classname");
-                String testName = testCase.getAttribute("name");
-                LocaleSafeDecimalFormat format = new LocaleSafeDecimalFormat();
-                BigDecimal duration = format.parse(testCase.getAttribute("time"));
-                duration = duration.multiply(BigDecimal.valueOf(1000));
-                NodeList failures = testCase.getElementsByTagName("failure");
-                TestResult testResult = model.addTest(className, testName, duration.longValue());
-                for (int j = 0; j < failures.getLength(); j++) {
-                    Element failure = (Element) failures.item(j);
-                    testResult.addFailure(failure.getAttribute("message"), failure.getTextContent());
-                }
-            }
-            NodeList ignoredTestCases = document.getElementsByTagName("ignored-testcase");
-            for (int i = 0; i < ignoredTestCases.getLength(); i++) {
-                Element testCase = (Element) ignoredTestCases.item(i);
-                String className = testCase.getAttribute("classname");
-                String testName = testCase.getAttribute("name");
-                model.addTest(className, testName, 0).ignored();
-            }
-            String suiteClassName = document.getDocumentElement().getAttribute("name");
-            ClassTestResults suiteResults = model.addTestClass(suiteClassName);
-            NodeList stdOutElements = document.getElementsByTagName("system-out");
-            for (int i = 0; i < stdOutElements.getLength(); i++) {
-                suiteResults.addStandardOutput(stdOutElements.item(i).getTextContent());
-            }
-            NodeList stdErrElements = document.getElementsByTagName("system-err");
-            for (int i = 0; i < stdErrElements.getLength(); i++) {
-                suiteResults.addStandardError(stdErrElements.item(i).getTextContent());
-            }
-        } catch (Exception e) {
-            throw new GradleException(String.format("Could not load test results from '%s'.", file), e);
+            StringWriter stringWriter = new StringWriter();
+            PrintWriter writer = new PrintWriter(stringWriter);
+            throwable.printStackTrace(writer);
+            writer.close();
+            return stringWriter.toString();
+        } catch (Throwable t) {
+            StringWriter stringWriter = new StringWriter();
+            PrintWriter writer = new PrintWriter(stringWriter);
+            t.printStackTrace(writer);
+            writer.close();
+            return stringWriter.toString();
         }
     }
 
-    private void generateFiles(AllTestResults model) {
+    private void generateFiles(AllTestResults model, TestResultsProvider resultsProvider, File reportDir) {
         try {
             generatePage(model, new OverviewPageRenderer(), new File(reportDir, "index.html"));
             for (PackageTestResults packageResults : model.getPackages()) {
                 generatePage(packageResults, new PackagePageRenderer(), new File(reportDir, packageResults.getName() + ".html"));
                 for (ClassTestResults classResults : packageResults.getClasses()) {
-                    generatePage(classResults, new ClassPageRenderer(), new File(reportDir, classResults.getName() + ".html"));
+                    generatePage(classResults, new ClassPageRenderer(classResults.getName(), resultsProvider), new File(reportDir, classResults.getName() + ".html"));
                 }
             }
         } catch (Exception e) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/TestReporter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/TestReporter.java
index 3463885..a6bf842 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/TestReporter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/TestReporter.java
@@ -15,12 +15,10 @@
  */
 package org.gradle.api.internal.tasks.testing.junit.report;
 
+import org.gradle.api.internal.tasks.testing.junit.result.TestResultsProvider;
+
 import java.io.File;
 
 public interface TestReporter {
-    void setTestResultsDir(File resultDir);
-
-    void setTestReportDir(File reportDir);
-
-    void generateReport();
+    void generateReport(TestResultsProvider testResultsProvider, File reportDir);
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/AbstractTestResultProvider.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/AbstractTestResultProvider.java
new file mode 100644
index 0000000..9a4f38b
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/AbstractTestResultProvider.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit.result;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.tasks.testing.TestOutputEvent;
+
+import java.io.*;
+
+public abstract class AbstractTestResultProvider implements TestResultsProvider {
+    private final File resultsDir;
+
+    protected AbstractTestResultProvider(File resultsDir) {
+        this.resultsDir = resultsDir;
+    }
+
+    public File getResultsDir() {
+        return resultsDir;
+    }
+
+    protected File outputsFile(String className, TestOutputEvent.Destination destination) {
+        return destination == TestOutputEvent.Destination.StdOut ? standardOutputFile(className) : standardErrorFile(className);
+    }
+
+    private File standardErrorFile(String className) {
+        return new File(resultsDir, className + ".stderr");
+    }
+
+    private File standardOutputFile(String className) {
+        return new File(resultsDir, className + ".stdout");
+    }
+
+    public void writeOutputs(String className, TestOutputEvent.Destination destination, Writer writer) {
+        final File file = outputsFile(className, destination);
+        if (!file.exists()) {
+            return;
+        }
+        try {
+            Reader reader = new InputStreamReader(new BufferedInputStream(new FileInputStream(file)), "UTF-8");
+            try {
+                char[] buffer = new char[2048];
+                while (true) {
+                    int read = reader.read(buffer);
+                    if (read < 0) {
+                        return;
+                    }
+                    writer.write(buffer, 0, read);
+                }
+            } finally {
+                reader.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/AggregateTestResultsProvider.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/AggregateTestResultsProvider.java
new file mode 100644
index 0000000..93b1fa2
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/AggregateTestResultsProvider.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit.result;
+
+import org.gradle.api.Action;
+import org.gradle.api.tasks.testing.TestOutputEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Map;
+
+public class AggregateTestResultsProvider implements TestResultsProvider {
+    private static final Logger LOGGER = LoggerFactory.getLogger(AggregateTestResultsProvider.class);
+    private final Iterable<File> binaryResultDirs;
+    private Map<String, TestResultsProvider> classOutputProviders;
+
+    public AggregateTestResultsProvider(Iterable<File> binaryResultDirs) {
+        this.binaryResultDirs = binaryResultDirs;
+    }
+
+    public void visitClasses(final Action<? super TestClassResult> visitor) {
+        classOutputProviders = new HashMap<String, TestResultsProvider>();
+        for (File dir : binaryResultDirs) {
+            final BinaryResultBackedTestResultsProvider provider = new BinaryResultBackedTestResultsProvider(dir);
+            provider.visitClasses(new Action<TestClassResult>() {
+                public void execute(TestClassResult classResult) {
+                    if (classOutputProviders.containsKey(classResult.getClassName())) {
+                        LOGGER.warn("Discarding duplicate results for test class {}.", classResult.getClassName());
+                        return;
+                    }
+                    classOutputProviders.put(classResult.getClassName(), provider);
+                    visitor.execute(classResult);
+                }
+            });
+        }
+    }
+
+    public void writeOutputs(String className, TestOutputEvent.Destination destination, Writer writer) {
+        classOutputProviders.get(className).writeOutputs(className, destination, writer);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/Binary2JUnitXmlReportGenerator.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/Binary2JUnitXmlReportGenerator.java
new file mode 100644
index 0000000..844ef3b
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/Binary2JUnitXmlReportGenerator.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit.result;
+
+import org.apache.commons.io.IOUtils;
+import org.gradle.api.Action;
+import org.gradle.api.GradleException;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.util.Clock;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+public class Binary2JUnitXmlReportGenerator {
+
+    private final File testResultsDir;
+    private final TestResultsProvider testResultsProvider;
+    JUnitXmlResultWriter saxWriter;
+    private final static Logger LOG = Logging.getLogger(Binary2JUnitXmlReportGenerator.class);
+
+    public Binary2JUnitXmlReportGenerator(File testResultsDir, TestResultsProvider testResultsProvider) {
+        this.testResultsDir = testResultsDir;
+        this.testResultsProvider = testResultsProvider;
+        this.saxWriter = new JUnitXmlResultWriter(getHostname(), testResultsProvider);
+    }
+
+    public void generate() {
+        Clock clock = new Clock();
+        testResultsProvider.visitClasses(new Action<TestClassResult>() {
+            public void execute(TestClassResult result) {
+                File file = new File(testResultsDir, "TEST-" + result.getClassName() + ".xml");
+                OutputStream output = null;
+                try {
+                    output = new BufferedOutputStream(new FileOutputStream(file));
+                    saxWriter.write(result, output);
+                    output.close();
+                } catch (Exception e) {
+                    throw new GradleException(String.format("Could not write XML test results for %s to file %s.", result.getClassName(), file), e);
+                } finally {
+                    IOUtils.closeQuietly(output);
+                }
+            }
+        });
+        LOG.info("Finished generating test XML results (" + clock.getTime() + ")");
+    }
+
+    private static String getHostname() {
+        try {
+            return InetAddress.getLocalHost().getHostName();
+        } catch (UnknownHostException e) {
+            return "localhost";
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/BinaryResultBackedTestResultsProvider.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/BinaryResultBackedTestResultsProvider.java
new file mode 100644
index 0000000..3c1ccb0
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/BinaryResultBackedTestResultsProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit.result;
+
+import org.gradle.api.Action;
+
+import java.io.File;
+
+public class BinaryResultBackedTestResultsProvider extends AbstractTestResultProvider {
+    public BinaryResultBackedTestResultsProvider(File resultsDir) {
+        super(resultsDir);
+    }
+
+    public void visitClasses(final Action<? super TestClassResult> visitor) {
+        new TestResultSerializer().read(getResultsDir(), visitor);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/CachingFileWriter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/CachingFileWriter.java
new file mode 100644
index 0000000..b38601b
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/CachingFileWriter.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit.result;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+import java.io.*;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.gradle.internal.CompositeStoppable.closeable;
+
+/**
+ * by Szczepan Faber, created at: 11/19/12
+ */
+public class CachingFileWriter {
+
+    private final static Logger LOG = Logging.getLogger(CachingFileWriter.class);
+
+    final LinkedHashMap<File, Writer> openFiles = new LinkedHashMap<File, Writer>();
+    private final int openFilesCount;
+
+    public CachingFileWriter(int openFilesCount) {
+        this.openFilesCount = openFilesCount;
+    }
+
+    public void write(File file, String text) {
+        Writer out;
+        try {
+            try {
+                if (openFiles.containsKey(file)) {
+                    out = openFiles.get(file);
+                } else {
+                    out = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(file, true)), "UTF-8");
+                    openFiles.put(file, out);
+                    if (openFiles.size() > openFilesCount) {
+                        //remove first
+                        Iterator<Map.Entry<File, Writer>> iterator = openFiles.entrySet().iterator();
+                        close(iterator.next().getValue(), file.toString());
+                        iterator.remove();
+                    }
+                }
+                out.write(text);
+            } catch (IOException e) {
+                throw new UncheckedIOException("Problems writing to file: " + file, e);
+            }
+        } catch (UncheckedIOException e) {
+            cleanUpQuietly();
+            throw e;
+        }
+    }
+
+    public void closeAll() {
+        try {
+            for (Map.Entry<File, Writer> entry : openFiles.entrySet()) {
+                close(entry.getValue(), entry.getKey().toString());
+            }
+        } catch (UncheckedIOException e) {
+            cleanUpQuietly();
+            throw e;
+        } finally {
+            openFiles.clear();
+        }
+    }
+
+    private void cleanUpQuietly() {
+        try {
+            closeable(openFiles.values()).stop();
+        } catch (Exception e) {
+            LOG.debug("Problems closing files", e);
+        } finally {
+            openFiles.clear();
+        }
+    }
+
+    private void close(Closeable c, String displayName) {
+        try {
+            c.close();
+        } catch (IOException e) {
+            throw new UncheckedIOException("Problems closing file: " + displayName, e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/JUnitXmlResultWriter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/JUnitXmlResultWriter.java
new file mode 100644
index 0000000..b2a06ee
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/JUnitXmlResultWriter.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit.result;
+
+import org.apache.tools.ant.util.DateUtils;
+import org.gradle.api.internal.xml.SimpleXmlWriter;
+import org.gradle.api.tasks.testing.TestOutputEvent;
+import org.gradle.api.tasks.testing.TestResult;
+import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.remote.internal.PlaceholderException;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * by Szczepan Faber, created at: 11/13/12
+ */
+public class JUnitXmlResultWriter {
+
+    private final String hostName;
+    private final TestResultsProvider testResultsProvider;
+
+    public JUnitXmlResultWriter(String hostName, TestResultsProvider testResultsProvider) {
+        this.hostName = hostName;
+        this.testResultsProvider = testResultsProvider;
+    }
+
+    public void write(TestClassResult result, OutputStream output) {
+        String className = result.getClassName();
+        try {
+            SimpleXmlWriter writer = new SimpleXmlWriter(output, "  ");
+            writer.startElement("testsuite")
+                    .attribute("name", className)
+                    .attribute("tests", String.valueOf(result.getTestsCount()))
+                    .attribute("failures", String.valueOf(result.getFailuresCount()))
+                    .attribute("errors", "0")
+                    .attribute("timestamp", DateUtils.format(result.getStartTime(), DateUtils.ISO8601_DATETIME_PATTERN))
+                    .attribute("hostname", hostName)
+                    .attribute("time", String.valueOf(result.getDuration() / 1000.0));
+
+            writer.startElement("properties");
+            writer.endElement();
+
+            writeTests(writer, result.getResults(), className);
+
+            writer.startElement("system-out");
+            writeOutputs(writer, className, TestOutputEvent.Destination.StdOut);
+            writer.endElement();
+            writer.startElement("system-err");
+            writeOutputs(writer, className, TestOutputEvent.Destination.StdErr);
+            writer.endElement();
+            writer.endElement();
+        } catch (IOException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    private void writeOutputs(SimpleXmlWriter writer, String className, TestOutputEvent.Destination destination) throws IOException {
+        writer.startCDATA();
+        testResultsProvider.writeOutputs(className, destination, writer);
+        writer.endCDATA();
+    }
+
+    private void writeTests(SimpleXmlWriter writer, Iterable<TestMethodResult> methodResults, String className) throws IOException {
+        for (TestMethodResult methodResult : methodResults) {
+            String testCase = methodResult.getResultType() == TestResult.ResultType.SKIPPED ? "ignored-testcase" : "testcase";
+            writer.startElement(testCase)
+                    .attribute("name", methodResult.getName())
+                    .attribute("classname", className)
+                    .attribute("time", String.valueOf(methodResult.getDuration() / 1000.0));
+
+            for (Throwable failure : methodResult.getExceptions()) {
+                writer.startElement("failure")
+                        .attribute("message", failureMessage(failure))
+                        .attribute("type", failure.getClass().getName());
+
+                writer.characters(stackTrace(failure));
+
+                writer.endElement();
+            }
+            writer.endElement();
+        }
+    }
+
+    private String failureMessage(Throwable throwable) {
+        try {
+            return throwable.toString();
+        } catch (Throwable t) {
+            String exceptionClassName = throwable instanceof PlaceholderException ? ((PlaceholderException)throwable).getExceptionClassName() : throwable.getClass().getName();
+            return String.format("Could not determine failure message for exception of type %s: %s",
+                    exceptionClassName, t);
+        }
+    }
+
+    private String stackTrace(Throwable throwable) {
+        try {
+            StringWriter stringWriter = new StringWriter();
+            PrintWriter writer = new PrintWriter(stringWriter);
+            throwable.printStackTrace(writer);
+            writer.close();
+            return stringWriter.toString();
+        } catch (Throwable t) {
+            StringWriter stringWriter = new StringWriter();
+            PrintWriter writer = new PrintWriter(stringWriter);
+            t.printStackTrace(writer);
+            writer.close();
+            return stringWriter.toString();
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestClassResult.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestClassResult.java
new file mode 100644
index 0000000..75f7bd6
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestClassResult.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit.result;
+
+import org.gradle.api.tasks.testing.TestResult;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * by Szczepan Faber, created at: 11/13/12
+ */
+public class TestClassResult {
+    private final List<TestMethodResult> methodResults = new ArrayList<TestMethodResult>();
+    private final String className;
+    private final long startTime;
+    private int failuresCount;
+
+    public TestClassResult(String className, long startTime) {
+        this.className = className;
+        this.startTime = startTime;
+    }
+
+    public String getClassName() {
+        return className;
+    }
+
+    public TestClassResult add(TestMethodResult methodResult) {
+        if (methodResult.getResultType() == TestResult.ResultType.FAILURE) {
+            failuresCount++;
+        }
+        methodResults.add(methodResult);
+        return this;
+    }
+
+    public List<TestMethodResult> getResults() {
+        return methodResults;
+    }
+
+    public long getStartTime() {
+        return startTime;
+    }
+
+    public int getTestsCount() {
+        return methodResults.size();
+    }
+
+    public int getFailuresCount() {
+        return failuresCount;
+    }
+
+    public long getDuration() {
+        long end = startTime;
+        for (TestMethodResult m : methodResults) {
+            if (end < m.getEndTime()) {
+                end = m.getEndTime();
+            }
+        }
+        return end - startTime;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestMethodResult.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestMethodResult.java
new file mode 100644
index 0000000..39121c2
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestMethodResult.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit.result;
+
+import org.gradle.api.tasks.testing.TestResult;
+
+import java.util.List;
+
+/**
+ * by Szczepan Faber, created at: 11/13/12
+ */
+public class TestMethodResult {
+    private final String name;
+    private final TestResult.ResultType resultType;
+    private final long duration;
+    private final long endTime;
+    private final List<Throwable> exceptions;
+
+    public TestMethodResult(String name, TestResult result) {
+        this.name = name;
+        resultType = result.getResultType();
+        duration = result.getEndTime() - result.getStartTime();
+        endTime = result.getEndTime();
+        exceptions = result.getExceptions();
+    }
+
+    public TestMethodResult(String name, TestResult.ResultType resultType, long duration, long endTime, List<Throwable> exceptions) {
+        this.name = name;
+        this.resultType = resultType;
+        this.duration = duration;
+        this.endTime = endTime;
+        this.exceptions = exceptions;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public List<Throwable> getExceptions() {
+        return exceptions;
+    }
+
+    public TestResult.ResultType getResultType() {
+        return resultType;
+    }
+
+    public long getDuration() {
+        return duration;
+    }
+
+    public long getEndTime() {
+        return endTime;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollector.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollector.java
new file mode 100644
index 0000000..82d861a
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollector.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit.result;
+
+import org.gradle.api.Action;
+import org.gradle.api.tasks.testing.*;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Assembles test results. Keeps a copy of the results in memory to provide them later and spools test output to file.
+ *
+ * by Szczepan Faber, created at: 11/13/12
+ */
+public class TestReportDataCollector extends AbstractTestResultProvider implements TestListener, TestOutputListener {
+    private final Map<String, TestClassResult> results = new HashMap<String, TestClassResult>();
+    private final TestResultSerializer serializer;
+    private final CachingFileWriter cachingFileWriter;
+
+    public TestReportDataCollector(File resultsDir) {
+        //TODO SF calculate number of open files based on parallel forks
+        this(resultsDir, new CachingFileWriter(10), new TestResultSerializer());
+    }
+
+    TestReportDataCollector(File resultsDir, CachingFileWriter cachingFileWriter, TestResultSerializer serializer) {
+        super(resultsDir);
+        this.cachingFileWriter = cachingFileWriter;
+        this.serializer = serializer;
+    }
+
+    public void beforeSuite(TestDescriptor suite) {
+    }
+
+    public void afterSuite(TestDescriptor suite, TestResult result) {
+        if (suite.getParent() == null) {
+            cachingFileWriter.closeAll();
+            writeResults();
+        }
+    }
+
+    private void writeResults() {
+        serializer.write(results.values(), getResultsDir());
+    }
+
+    public void beforeTest(TestDescriptor testDescriptor) {
+    }
+
+    public void afterTest(TestDescriptor testDescriptor, TestResult result) {
+        if (!testDescriptor.isComposite()) {
+            String className = testDescriptor.getClassName();
+            TestMethodResult methodResult = new TestMethodResult(testDescriptor.getName(), result);
+            TestClassResult classResult = results.get(className);
+            if (classResult == null) {
+                classResult = new TestClassResult(className, result.getStartTime());
+                results.put(className, classResult);
+            }
+            classResult.add(methodResult);
+        }
+    }
+
+    public void onOutput(TestDescriptor testDescriptor, TestOutputEvent outputEvent) {
+        String className = testDescriptor.getClassName();
+        if (className == null) {
+            //this means that we receive an output before even starting any class (or too late).
+            //we don't have a place for such output in any of the reports so skipping.
+            return;
+        }
+        cachingFileWriter.write(outputsFile(className, outputEvent.getDestination()), outputEvent.getMessage());
+    }
+
+    public void visitClasses(Action<? super TestClassResult> visitor) {
+        for (TestClassResult classResult : results.values()) {
+            visitor.execute(classResult);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultSerializer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultSerializer.java
new file mode 100644
index 0000000..51a3f3a
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultSerializer.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit.result;
+
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import org.gradle.api.Action;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.tasks.testing.TestResult;
+import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.remote.internal.Message;
+
+import java.io.*;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public class TestResultSerializer {
+    private static final int RESULT_VERSION = 1;
+    private static final String RESULTS_FILE_NAME = "results.bin";
+
+    public void write(Collection<TestClassResult> results, File outputDir) {
+        try {
+            OutputStream outputStream = new FileOutputStream(new File(outputDir, RESULTS_FILE_NAME));
+            try {
+                Output output = new Output(outputStream);
+                output.writeInt(RESULT_VERSION, true);
+                write(results, output);
+                output.flush();
+            } finally {
+                outputStream.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private void write(Collection<TestClassResult> results, Output output) throws IOException {
+        output.writeInt(results.size(), true);
+        for (TestClassResult result : results) {
+            write(result, output);
+        }
+    }
+
+    private void write(TestClassResult classResult, Output output) throws IOException {
+        output.writeString(classResult.getClassName());
+        output.writeLong(classResult.getStartTime());
+        output.writeInt(classResult.getResults().size(), true);
+        for (TestMethodResult methodResult : classResult.getResults()) {
+            write(methodResult, output);
+        }
+    }
+
+    private void write(TestMethodResult methodResult, Output output) throws IOException {
+        output.writeString(methodResult.getName());
+        output.writeInt(methodResult.getResultType().ordinal(), true);
+        output.writeLong(methodResult.getDuration(), true);
+        output.writeLong(methodResult.getEndTime());
+        if (methodResult.getExceptions().isEmpty()) {
+            output.writeBoolean(false);
+        } else {
+            output.writeBoolean(true);
+            Message.send(methodResult.getExceptions(), output);
+        }
+    }
+
+    public void read(File inputDir, Action<? super TestClassResult> visitor) {
+        try {
+            InputStream inputStream = new FileInputStream(new File(inputDir, "results.bin"));
+            try {
+                Input input = new Input(inputStream);
+                int version = input.readInt(true);
+                if (version != RESULT_VERSION) {
+                    throw new IllegalArgumentException(String.format("Unexpected result file version %d found in %s.", version, inputDir));
+                }
+                readResults(input, visitor);
+            } finally {
+                inputStream.close();
+            }
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    private void readResults(Input input, Action<? super TestClassResult> visitor) throws ClassNotFoundException, IOException {
+        int classCount = input.readInt(true);
+        for (int i = 0; i < classCount; i++) {
+            TestClassResult classResult = readClassResult(input);
+            visitor.execute(classResult);
+        }
+    }
+
+    private TestClassResult readClassResult(Input input) throws IOException, ClassNotFoundException {
+        String className = input.readString();
+        long startTime = input.readLong();
+        TestClassResult result = new TestClassResult(className, startTime);
+        int testMethodCount = input.readInt(true);
+        for (int i = 0; i < testMethodCount; i++) {
+            TestMethodResult methodResult = readMethodResult(input);
+            result.add(methodResult);
+        }
+        return result;
+    }
+
+    private TestMethodResult readMethodResult(Input input) throws ClassNotFoundException, IOException {
+        String name = input.readString();
+        TestResult.ResultType resultType = TestResult.ResultType.values()[input.readInt(true)];
+        long duration = input.readLong(true);
+        long endTime = input.readLong();
+        boolean hasFailures = input.readBoolean();
+        List<Throwable> failures;
+        if (hasFailures) {
+            failures = (List<Throwable>) Message.receive(input, getClass().getClassLoader());
+        } else {
+            failures = Collections.emptyList();
+        }
+        return new TestMethodResult(name, resultType, duration, endTime, failures);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultsProvider.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultsProvider.java
new file mode 100644
index 0000000..e98d04f
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultsProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit.result;
+
+import org.gradle.api.Action;
+import org.gradle.api.tasks.testing.TestOutputEvent;
+
+import java.io.Writer;
+
+/**
+ * by Szczepan Faber, created at: 11/16/12
+ */
+public interface TestResultsProvider {
+    /**
+     * Writes the output of the given test to the given writer. This method must be called only after {@link #visitClasses(org.gradle.api.Action)}.
+     */
+    void writeOutputs(String className, TestOutputEvent.Destination destination, Writer writer);
+
+    /**
+     * Visits the results of each test class, in no specific order. Each class is visited exactly once.
+     */
+    void visitClasses(Action<? super TestClassResult> visitor);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/XmlTestSuite.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/XmlTestSuite.java
deleted file mode 100644
index ed74e73..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/XmlTestSuite.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.tasks.testing.junit.result;
-
-import org.apache.tools.ant.util.DOMElementWriter;
-import org.apache.tools.ant.util.DateUtils;
-import org.gradle.api.GradleException;
-import org.gradle.api.tasks.testing.TestOutputEvent;
-import org.gradle.api.tasks.testing.TestResult;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-
-import java.io.*;
-import java.util.Collection;
-import java.util.EnumMap;
-import java.util.Map;
-
-/**
- * by Szczepan Faber, created at: 10/3/12
- */
-public class XmlTestSuite {
-
-    private final Document testSuiteReport;
-    private final Element rootElement;
-    private final String hostname;
-    private final String className;
-    private final long startTime;
-    private final Map<TestOutputEvent.Destination, StringBuffer> outputs
-            = new EnumMap<TestOutputEvent.Destination, StringBuffer>(TestOutputEvent.Destination.class);
-    private final File reportFile;
-    private long failedCount;
-    private long testCount;
-
-    public XmlTestSuite(File testResultsDir, String className, long startTime, String hostname, Document document) {
-        this.className = className;
-        this.startTime = startTime;
-        this.hostname = hostname;
-        testSuiteReport = document;
-        rootElement = testSuiteReport.createElement("testsuite");
-        testSuiteReport.appendChild(rootElement);
-        // Add an empty properties element for compatibility
-        rootElement.appendChild(testSuiteReport.createElement("properties"));
-
-        outputs.put(TestOutputEvent.Destination.StdOut, new StringBuffer());
-        outputs.put(TestOutputEvent.Destination.StdErr, new StringBuffer());
-
-        reportFile = new File(testResultsDir, "TEST-" + className + ".xml");
-    }
-
-    public String getClassName() {
-        return className;
-    }
-
-    public void addTestCase(String testName, TestResult.ResultType resultType, long executionTime, Collection<Throwable> failures) {
-        String testCase = resultType == TestResult.ResultType.SKIPPED ? "ignored-testcase" : "testcase";
-        Element element = testSuiteReport.createElement(testCase);
-        element.setAttribute("name", testName);
-        element.setAttribute("classname", this.className);
-        element.setAttribute("time", String.valueOf(executionTime / 1000.0));
-        if (!failures.isEmpty()) {
-            failedCount++;
-            for (Throwable failure : failures) {
-                Element failureElement = testSuiteReport.createElement("failure");
-                element.appendChild(failureElement);
-                failureElement.setAttribute("message", failureMessage(failure));
-                failureElement.setAttribute("type", failure.getClass().getName());
-                failureElement.appendChild(testSuiteReport.createTextNode(stackTrace(failure)));
-            }
-        }
-        testCount++;
-        rootElement.appendChild(element);
-    }
-
-    public void writeSuiteData(long executionTime) {
-        rootElement.setAttribute("name", this.className);
-        rootElement.setAttribute("tests", String.valueOf(this.testCount));
-        rootElement.setAttribute("failures", String.valueOf(this.failedCount));
-        rootElement.setAttribute("errors", "0");
-        rootElement.setAttribute("timestamp", DateUtils.format(this.startTime, DateUtils.ISO8601_DATETIME_PATTERN));
-        rootElement.setAttribute("hostname", this.hostname);
-        Element stdoutElement = testSuiteReport.createElement("system-out");
-        stdoutElement.appendChild(testSuiteReport.createCDATASection(outputs.get(TestOutputEvent.Destination.StdOut)
-                .toString()));
-        rootElement.appendChild(stdoutElement);
-        Element stderrElement = testSuiteReport.createElement("system-err");
-        stderrElement.appendChild(testSuiteReport.createCDATASection(outputs.get(TestOutputEvent.Destination.StdErr)
-                .toString()));
-        rootElement.appendChild(stderrElement);
-        rootElement.setAttribute("time", String.valueOf(executionTime / 1000.0));
-        outputs.clear();
-
-        writeTo(reportFile);
-    }
-
-    private String stackTrace(Throwable throwable) {
-        try {
-            StringWriter stringWriter = new StringWriter();
-            PrintWriter writer = new PrintWriter(stringWriter);
-            throwable.printStackTrace(writer);
-            writer.close();
-            return stringWriter.toString();
-        } catch (Throwable t) {
-            StringWriter stringWriter = new StringWriter();
-            PrintWriter writer = new PrintWriter(stringWriter);
-            t.printStackTrace(writer);
-            writer.close();
-            return stringWriter.toString();
-        }
-    }
-
-    private String failureMessage(Throwable throwable) {
-        try {
-            return throwable.toString();
-        } catch (Throwable t) {
-            return String.format("Could not determine failure message for exception of type %s: %s",
-                    throwable.getClass().getName(), t);
-        }
-    }
-
-    public void writeTo(File reportFile) {
-        try {
-            OutputStream outstr = new BufferedOutputStream(new FileOutputStream(reportFile));
-            try {
-                new DOMElementWriter(true).write(rootElement, outstr);
-            } finally {
-                outstr.close();
-            }
-        } catch (IOException e) {
-            throw new GradleException(String.format("Could not write test report file '%s'.", reportFile), e);
-        }
-    }
-
-    public void addOutput(TestOutputEvent.Destination destination, String message) {
-        outputs.get(destination).append(message);
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/XmlTestSuiteFactory.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/XmlTestSuiteFactory.java
deleted file mode 100644
index 9e02b9d..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/XmlTestSuiteFactory.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.tasks.testing.junit.result;
-
-import org.gradle.internal.UncheckedException;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import java.io.File;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-public class XmlTestSuiteFactory {
-    private final String hostname;
-    private final DocumentBuilder documentBuilder;
-
-    public XmlTestSuiteFactory() {
-        hostname = getHostname();
-        try {
-            documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
-        } catch (Exception e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-    }
-
-    public XmlTestSuite create(File testResultsDir, String className, long startTime) {
-        return new XmlTestSuite(testResultsDir, className, startTime, hostname, documentBuilder.newDocument());
-    }
-
-    private String getHostname() {
-        try {
-            return InetAddress.getLocalHost().getHostName();
-        } catch (UnknownHostException e) {
-            return "localhost";
-        }
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/DefaultTestResult.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/DefaultTestResult.java
index 8fc3882..793db45 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/DefaultTestResult.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/DefaultTestResult.java
@@ -23,7 +23,7 @@ import java.util.List;
 
 public class DefaultTestResult implements TestResult, Serializable {
     private final List<Throwable> failures;
-    private final ResultType result;
+    private final ResultType resultType;
     private final long startTime;
     private final long endTime;
     private final long testCount;
@@ -31,17 +31,21 @@ public class DefaultTestResult implements TestResult, Serializable {
     private final long failedCount;
 
     public DefaultTestResult(TestState state) {
-        this.failures = state.failures;
-        this.result = state.resultType;
-        this.startTime = state.getStartTime();
-        this.endTime = state.getEndTime();
-        this.testCount = state.testCount;
-        this.successfulCount = state.successfulCount;
-        this.failedCount = state.failedCount;
+        this(state.resultType, state.getStartTime(), state.getEndTime(), state.testCount, state.successfulCount, state.failedCount, state.failures);
+    }
+
+    public DefaultTestResult(ResultType resultType, long startTime, long endTime, long testCount, long successfulCount, long failedCount, List<Throwable> failures) {
+        this.resultType = resultType;
+        this.startTime = startTime;
+        this.endTime = endTime;
+        this.testCount = testCount;
+        this.successfulCount = successfulCount;
+        this.failedCount = failedCount;
+        this.failures = failures;
     }
 
     public ResultType getResultType() {
-        return result;
+        return resultType;
     }
 
     public Throwable getException() {
@@ -78,6 +82,6 @@ public class DefaultTestResult implements TestResult, Serializable {
 
     @Override
     public String toString() {
-        return result.toString();
+        return resultType.toString();
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/StateTrackingTestResultProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/StateTrackingTestResultProcessor.java
index 4e1e793..60168cc 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/StateTrackingTestResultProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/StateTrackingTestResultProcessor.java
@@ -103,8 +103,4 @@ public abstract class StateTrackingTestResultProcessor implements TestResultProc
 
     protected void completed(TestState state) {
     }
-
-    protected TestState getTestStateFor(Object testId) {
-        return executing.get(testId);
-    }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.java
index 5dad475..bc90fb9 100755
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.java
@@ -21,11 +21,9 @@ import org.gradle.api.GradleException;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
 import org.gradle.api.internal.tasks.testing.TestResultProcessor;
-import org.gradle.api.internal.tasks.testing.junit.TestNGJUnitXmlReportGenerator;
 import org.gradle.api.internal.tasks.testing.processors.CaptureTestOutputTestResultProcessor;
 import org.gradle.api.tasks.testing.testng.TestNGOptions;
 import org.gradle.internal.id.IdGenerator;
-import org.gradle.listener.ListenerBroadcast;
 import org.gradle.logging.StandardOutputRedirector;
 import org.gradle.util.CollectionUtils;
 import org.gradle.util.GFileUtils;
@@ -44,31 +42,22 @@ public class TestNGTestClassProcessor implements TestClassProcessor {
     private final List<File> suiteFiles;
     private final IdGenerator<?> idGenerator;
     private final StandardOutputRedirector outputRedirector;
-    private final File testResultsDir;
     private final boolean testReportOn;
     private TestNGTestResultProcessorAdapter testResultProcessor;
     private ClassLoader applicationClassLoader;
 
     public TestNGTestClassProcessor(File testReportDir, TestNGOptions options, List<File> suiteFiles, IdGenerator<?> idGenerator,
-                                    StandardOutputRedirector outputRedirector, File testResultsDir, boolean testReportOn) {
+                                    StandardOutputRedirector outputRedirector, boolean testReportOn) {
         this.testReportDir = testReportDir;
         this.options = options;
         this.suiteFiles = suiteFiles;
         this.idGenerator = idGenerator;
         this.outputRedirector = outputRedirector;
-        this.testResultsDir = testResultsDir;
         this.testReportOn = testReportOn;
     }
 
     public void startProcessing(TestResultProcessor resultProcessor) {
-        ListenerBroadcast<TestResultProcessor> processors = new ListenerBroadcast<TestResultProcessor>(TestResultProcessor.class);
-        processors.add(resultProcessor);
-        if (testReportOn) {
-            //Do not generate the xml results unless the report is wanted explicitly
-            //TODO SF this check needs to be removed when new TestNG reports are turned on by default
-            processors.add(new TestNGJUnitXmlReportGenerator(testResultsDir));
-        }
-        TestResultProcessor resultProcessorChain = new CaptureTestOutputTestResultProcessor(processors.getSource(), outputRedirector);
+        TestResultProcessor resultProcessorChain = new CaptureTestOutputTestResultProcessor(resultProcessor, outputRedirector);
 
         testResultProcessor = new TestNGTestResultProcessorAdapter(resultProcessorChain, idGenerator);
 
@@ -99,11 +88,7 @@ public class TestNGTestClassProcessor implements TestClassProcessor {
             testNg.setSourcePath(CollectionUtils.join(File.pathSeparator, options.getTestResources()));
         }
 
-        //only use the default listeners flag when testReport is off
-        //TODO SF spockify the test, add unit test coverage and inform about 'incubating' nature of the new report
-        //we should remove this complexity when new TestNG reports are turned on by default
-        testNg.setUseDefaultListeners(!testReportOn && options.getUseDefaultListeners());
-
+        testNg.setUseDefaultListeners(options.getUseDefaultListeners());
         testNg.addListener((Object) adaptListener(testResultProcessor));
         testNg.setVerbose(0);
         testNg.setGroups(CollectionUtils.join(",", options.getIncludeGroups()));
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java
index d6208b2..6a8f279 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java
@@ -23,10 +23,6 @@ import org.gradle.api.internal.tasks.testing.TestFramework;
 import org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory;
 import org.gradle.api.internal.tasks.testing.detection.ClassFileExtractionManager;
 import org.gradle.api.internal.tasks.testing.junit.JULRedirector;
-import org.gradle.api.internal.tasks.testing.junit.report.DefaultTestReport;
-import org.gradle.api.internal.tasks.testing.junit.report.TestReporter;
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.Logging;
 import org.gradle.api.tasks.testing.Test;
 import org.gradle.api.tasks.testing.testng.TestNGOptions;
 import org.gradle.internal.id.IdGenerator;
@@ -41,27 +37,22 @@ import java.util.List;
  * @author Tom Eyckmans
  */
 public class TestNGTestFramework implements TestFramework {
-
-    private final static Logger LOG = Logging.getLogger(TestNGTestFramework.class);
-
     private TestNGOptions options;
     private TestNGDetector detector;
-    private final Test testTask;
-    private TestReporter reporter;
+    final Test testTask;
 
     public TestNGTestFramework(Test testTask) {
         this.testTask = testTask;
         options = new TestNGOptions(testTask.getProject().getProjectDir());
         options.setAnnotationsOnSourceCompatibility(JavaVersion.toVersion(testTask.getProject().property("sourceCompatibility")));
         detector = new TestNGDetector(new ClassFileExtractionManager(testTask.getTemporaryDirFactory()));
-        reporter = new DefaultTestReport();
     }
 
     public WorkerTestClassProcessorFactory getProcessorFactory() {
         options.setTestResources(testTask.getTestSrcDirs());
         List<File> suiteFiles = options.getSuites(testTask.getTemporaryDir());
         return new TestClassProcessorFactoryImpl(testTask.getTestReportDir(), options, suiteFiles,
-                testTask.getTestResultsDir(), testTask.isTestReport());
+                testTask.isTestReport());
     }
 
     public Action<WorkerProcessBuilder> getWorkerConfigurationAction() {
@@ -72,18 +63,6 @@ public class TestNGTestFramework implements TestFramework {
         };
     }
 
-    public void report() {
-        if (!testTask.isTestReport()) {
-            LOG.info("Test report disabled, omitting generation of the html test report.");
-            return;
-        }
-        // TODO SF make the logging consistent in frameworks, add coverage and spockify the test
-        LOG.info("Generating html test report...");
-        reporter.setTestReportDir(testTask.getTestReportDir());
-        reporter.setTestResultsDir(testTask.getTestResultsDir());
-        reporter.generateReport();
-    }
-
     public TestNGOptions getOptions() {
         return options;
     }
@@ -100,22 +79,19 @@ public class TestNGTestFramework implements TestFramework {
         private final File testReportDir;
         private final TestNGOptions options;
         private final List<File> suiteFiles;
-        private final File testResultsDir;
         private final boolean testReportOn;
 
         public TestClassProcessorFactoryImpl(File testReportDir, TestNGOptions options, List<File> suiteFiles,
-                                             File testResultsDir, boolean testReportOn) {
+                                             boolean testReportOn) {
             this.testReportDir = testReportDir;
             this.options = options;
             this.suiteFiles = suiteFiles;
-            this.testResultsDir = testResultsDir;
             this.testReportOn = testReportOn;
         }
 
         public TestClassProcessor create(ServiceRegistry serviceRegistry) {
             return new TestNGTestClassProcessor(testReportDir, options, suiteFiles,
-                    serviceRegistry.get(IdGenerator.class), new JULRedirector(),
-                    testResultsDir, testReportOn);
+                    serviceRegistry.get(IdGenerator.class), new JULRedirector(), testReportOn);
         }
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpec.java b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpec.java
index 531af92..243581c 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpec.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpec.java
@@ -17,8 +17,8 @@ package org.gradle.api.java.archives.internal;
 
 import com.google.common.collect.Sets;
 import groovy.lang.Closure;
-import org.codehaus.groovy.runtime.DefaultGroovyMethods;
 import org.gradle.api.Action;
+import org.gradle.api.internal.ClosureBackedAction;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.java.archives.Attributes;
 import org.gradle.api.java.archives.Manifest;
@@ -44,7 +44,7 @@ public class DefaultManifestMergeSpec implements ManifestMergeSpec {
     }
 
     public ManifestMergeSpec eachEntry(Closure mergeAction) {
-        return eachEntry((Action<? super ManifestMergeDetails>) DefaultGroovyMethods.asType(mergeAction, Action.class));
+        return eachEntry(new ClosureBackedAction<ManifestMergeDetails>(mergeAction));
     }
 
     public DefaultManifest merge(Manifest baseManifest, FileResolver fileResolver) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPlugin.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPlugin.groovy
index 8dda0bb..305fc76 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPlugin.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPlugin.groovy
@@ -22,6 +22,8 @@ import org.gradle.api.tasks.JavaExec
 import org.gradle.api.tasks.Sync
 import org.gradle.api.tasks.application.CreateStartScripts
 import org.gradle.api.tasks.bundling.Zip
+import org.gradle.api.tasks.bundling.Tar
+import org.gradle.api.tasks.bundling.AbstractArchiveTask
 import org.gradle.api.GradleException
 
 /**
@@ -37,6 +39,7 @@ class ApplicationPlugin implements Plugin<Project> {
     static final String TASK_START_SCRIPTS_NAME = "startScripts"
     static final String TASK_INSTALL_NAME = "installApp"
     static final String TASK_DIST_ZIP_NAME = "distZip"
+    static final String TASK_DIST_TAR_NAME = "distTar"
 
     private Project project
     private ApplicationPluginConvention pluginConvention
@@ -53,6 +56,7 @@ class ApplicationPlugin implements Plugin<Project> {
 
         addInstallTask()
         addDistZipTask()
+        addDistTarTask()
     }
 
     private void addPluginConvention() {
@@ -101,12 +105,20 @@ class ApplicationPlugin implements Plugin<Project> {
     }
 
     private void addDistZipTask() {
-        def distZipTask = project.tasks.add(TASK_DIST_ZIP_NAME, Zip)
-        distZipTask.description = "Bundles the project as a JVM application with libs and OS specific scripts."
-        distZipTask.group = APPLICATION_GROUP
-        distZipTask.conventionMapping.baseName = { pluginConvention.applicationName }
-        def baseDir = { distZipTask.archiveName - ".zip" }
-        distZipTask.into(baseDir) {
+        addArchiveTask(TASK_DIST_ZIP_NAME, Zip)
+    }
+
+	private void addDistTarTask() {
+        addArchiveTask(TASK_DIST_TAR_NAME, Tar)
+	}
+
+    private <T extends AbstractArchiveTask> void addArchiveTask(String name, Class<T> type) {
+        def archiveTask = project.tasks.add(name, type)
+        archiveTask.description = "Bundles the project as a JVM application with libs and OS specific scripts."
+        archiveTask.group = APPLICATION_GROUP
+        archiveTask.conventionMapping.baseName = { pluginConvention.applicationName }
+        def baseDir = { archiveTask.archiveName - ".${archiveTask.extension}" }
+        archiveTask.into(baseDir) {
             with(pluginConvention.applicationDistribution)
         }
     }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/DistributionExtension.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/DistributionExtension.groovy
new file mode 100755
index 0000000..6ab3eec
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/DistributionExtension.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.plugins
+
+import org.gradle.api.Incubating
+
+/**
+ * Extension for {@link JavaLibraryDistributionPlugin }
+ * 
+ *  
+ * <p>The extension for the java-library-distribution plugin.</p>
+ * <p>
+ * Use this class to configure the name of the distribution.
+ * <pre autoTested=''>
+ * apply plugin: 'java-library-distribution'
+ *
+ * distribution {
+ *   name = 'my-name'
+ * }
+ * </pre>
+ * <p>
+ * @author scogneau
+ *
+ */
+ at Incubating
+class DistributionExtension {
+
+	/**
+	 * The name of the distribution
+	 */
+	String name;
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyBasePlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyBasePlugin.java
index 77887ae..7b85715 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyBasePlugin.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyBasePlugin.java
@@ -16,11 +16,17 @@
 
 package org.gradle.api.plugins;
 
+import com.google.common.collect.Lists;
 import org.gradle.api.Action;
+import org.gradle.api.Nullable;
 import org.gradle.api.Plugin;
-import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTreeElement;
+import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.plugins.DslObject;
+import org.gradle.api.internal.plugins.GroovyJarFile;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.DefaultGroovySourceSet;
 import org.gradle.api.internal.tasks.DefaultSourceSet;
@@ -30,46 +36,56 @@ import org.gradle.api.tasks.SourceSet;
 import org.gradle.api.tasks.compile.GroovyCompile;
 import org.gradle.api.tasks.javadoc.Groovydoc;
 
+import javax.inject.Inject;
 import java.io.File;
+import java.util.List;
 import java.util.concurrent.Callable;
 
 /**
- * <p>A {@link org.gradle.api.Plugin} which extends the {@link org.gradle.api.plugins.JavaBasePlugin} to provide support for compiling and documenting Groovy
- * source files.</p>
+ * Extends {@link org.gradle.api.plugins.JavaBasePlugin} to provide support for compiling and documenting Groovy
+ * source files.
  *
  * @author Hans Dockter
  */
 public class GroovyBasePlugin implements Plugin<ProjectInternal> {
     public static final String GROOVY_CONFIGURATION_NAME = "groovy";
 
+    private final FileResolver fileResolver;
+    private ProjectInternal project;
+
+    @Inject
+    public GroovyBasePlugin(FileResolver fileResolver) {
+        this.fileResolver = fileResolver;
+    }
+
     public void apply(ProjectInternal project) {
+        this.project = project;
         JavaBasePlugin javaBasePlugin = project.getPlugins().apply(JavaBasePlugin.class);
 
-        project.getConfigurations().add(GROOVY_CONFIGURATION_NAME).setVisible(false).setTransitive(false).
-                setDescription("The groovy libraries to be used for this Groovy project.");
+        project.getConfigurations().add(GROOVY_CONFIGURATION_NAME).setVisible(false).
+                setDescription("The Groovy libraries to be used for this Groovy project.");
 
-        configureCompileDefaults(project);
-        configureSourceSetDefaults(project, javaBasePlugin);
+        configureCompileDefaults();
+        configureSourceSetDefaults(javaBasePlugin);
 
-        configureGroovydoc(project);
+        configureGroovydoc();
     }
 
-    private void configureCompileDefaults(final Project project) {
+    private void configureCompileDefaults() {
         project.getTasks().withType(GroovyCompile.class, new Action<GroovyCompile>() {
-            public void execute(GroovyCompile compile) {
-                compile.getConventionMapping().map("groovyClasspath", new Callable<Object>() {
+            public void execute(final GroovyCompile compile) {                                                                                                                                                                                                                                            compile.getConventionMapping().map("groovyClasspath", new Callable<Object>() {
                     public Object call() throws Exception {
-                        return project.getConfigurations().getByName(GROOVY_CONFIGURATION_NAME).copy().setTransitive(true);
+                        return getGroovyClasspath(compile.getClasspath());
                     }
                 });
             }
         });
     }
 
-    private void configureSourceSetDefaults(final ProjectInternal project, final JavaBasePlugin javaBasePlugin) {
+    private void configureSourceSetDefaults(final JavaBasePlugin javaBasePlugin) {
         project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().all(new Action<SourceSet>() {
             public void execute(SourceSet sourceSet) {
-                final DefaultGroovySourceSet groovySourceSet = new DefaultGroovySourceSet(((DefaultSourceSet) sourceSet).getDisplayName(), project.getFileResolver());
+                final DefaultGroovySourceSet groovySourceSet = new DefaultGroovySourceSet(((DefaultSourceSet) sourceSet).getDisplayName(), fileResolver);
                 new DslObject(sourceSet).getConvention().getPlugins().put("groovy", groovySourceSet);
 
                 groovySourceSet.getGroovy().srcDir(String.format("src/%s/groovy", sourceSet.getName()));
@@ -93,12 +109,12 @@ public class GroovyBasePlugin implements Plugin<ProjectInternal> {
         });
     }
 
-    private void configureGroovydoc(final Project project) {
+    private void configureGroovydoc() {
         project.getTasks().withType(Groovydoc.class, new Action<Groovydoc>() {
-            public void execute(Groovydoc groovydoc) {
+            public void execute(final Groovydoc groovydoc) {
                 groovydoc.getConventionMapping().map("groovyClasspath", new Callable<Object>() {
                     public Object call() throws Exception {
-                        return project.getConfigurations().getByName(GROOVY_CONFIGURATION_NAME).copy().setTransitive(true);
+                        return getGroovyClasspath(groovydoc.getClasspath());
                     }
                 });
                 groovydoc.getConventionMapping().map("destinationDir", new Callable<Object>() {
@@ -120,7 +136,43 @@ public class GroovyBasePlugin implements Plugin<ProjectInternal> {
         });
     }
 
+    private FileCollection getGroovyClasspath(FileCollection classpath) {
+        Configuration groovyConfiguration = project.getConfigurations().getByName(GROOVY_CONFIGURATION_NAME);
+        if (!groovyConfiguration.getDependencies().isEmpty()) { return groovyConfiguration; }
+
+        GroovyJarFile groovyJar = findGroovyJarFile(classpath);
+        if (groovyJar == null) { return groovyConfiguration; }
+
+        if (groovyJar.isGroovyAll()) {
+            return project.files(groovyJar.getFile());
+        }
+
+        if (project.getRepositories().isEmpty()) {
+            return groovyConfiguration;
+        }
+
+        String notation = groovyJar.getDependencyNotation();
+        List<Dependency> dependencies = Lists.newArrayList();
+        // project.getDependencies().create(String) seems to be the only feasible way to create a Dependency with a classifier
+        dependencies.add(project.getDependencies().create(notation));
+        if (groovyJar.getVersion().getMajor() >= 2) {
+            // add groovy-ant to bring in AntGroovyCompiler
+            dependencies.add(project.getDependencies().create(notation.replace(":groovy:", ":groovy-ant:")));
+        }
+        return project.getConfigurations().detachedConfiguration(dependencies.toArray(new Dependency[dependencies.size()]));
+    }
+
     private JavaPluginConvention java(Convention convention) {
         return convention.getPlugin(JavaPluginConvention.class);
     }
+
+    @Nullable
+    private GroovyJarFile findGroovyJarFile(Iterable<File> classpath) {
+        if (classpath == null) { return null; }
+        for (File file : classpath) {
+            GroovyJarFile groovyJar = GroovyJarFile.parse(file);
+            if (groovyJar != null) { return groovyJar; }
+        }
+        return null;
+    }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaBasePlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaBasePlugin.java
index 4dd10a7..874ca4e 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaBasePlugin.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaBasePlugin.java
@@ -23,7 +23,6 @@ import org.gradle.api.internal.ConventionMapping;
 import org.gradle.api.internal.IConventionAware;
 import org.gradle.api.internal.plugins.ProcessResources;
 import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.tasks.testing.testng.TestNGTestFramework;
 import org.gradle.api.reporting.ReportingExtension;
 import org.gradle.api.tasks.Copy;
 import org.gradle.api.tasks.SourceSet;
@@ -323,12 +322,11 @@ public class JavaBasePlugin implements Plugin<Project> {
                 return convention.getTestReportDir();
             }
         });
-        test.workingDir(project.getProjectDir());
-        //TODO SF move coverage from below to the JavaBasePluginSpec
-        test.getConventionMapping().map("testReport", new Callable<Object>() {
+        test.getConventionMapping().map("binResultsDir", new Callable<Object>() {
             public Object call() throws Exception {
-                return !(test.getTestFramework() instanceof TestNGTestFramework);
+                return new File(convention.getTestResultsDir(), String.format("binary/%s", test.getName()));
             }
         });
+        test.workingDir(project.getProjectDir());
     }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaLibraryDistributionPlugin.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaLibraryDistributionPlugin.groovy
new file mode 100755
index 0000000..a36e9ef
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaLibraryDistributionPlugin.groovy
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.plugins
+
+import org.gradle.api.GradleException
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.tasks.bundling.Zip
+
+/**
+ * A {@link Plugin} which package project as a distribution including
+ *  JAR, API documentation and source JAR for the project.
+ * @author scogneau
+ *
+ */
+ at Incubating
+class JavaLibraryDistributionPlugin implements Plugin<Project> {
+    static final String JAVA_LIBRARY_PLUGIN_NAME = "java-library-distribution"
+    static final String JAVA_LIBRARY_GROUP = JAVA_LIBRARY_PLUGIN_NAME
+    static final String TASK_DIST_ZIP_NAME = "distZip"
+
+    private DistributionExtension extension
+    private Project project
+
+    public void apply(Project project) {
+        this.project = project
+        project.plugins.apply(JavaPlugin)
+        addPluginExtension()
+        addDistZipTask()
+    }
+
+    private void addPluginExtension() {
+        extension = project.extensions.create("distribution", DistributionExtension)
+        extension.name = project.name
+    }
+
+    private void addDistZipTask() {
+        def distZipTask = project.tasks.add(TASK_DIST_ZIP_NAME, Zip)
+        distZipTask.description = "Bundles the project as a java library distribution."
+        distZipTask.group = JAVA_LIBRARY_GROUP
+        distZipTask.conventionMapping.baseName = {
+            if (project.distribution.name == null) {
+                throw new GradleException("Distribution name must not be null! Check your configuration of the java-library-distribution plugin.")
+            }
+            extension.name
+        }
+        def jar = project.tasks[JavaPlugin.JAR_TASK_NAME]
+        distZipTask.with {
+            from(jar)
+            from(project.file("src/dist"))
+            into("lib") {
+                from(project.configurations.runtime)
+            }
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPlugin.java
index 8e982c3..f16e15b 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPlugin.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPlugin.java
@@ -23,6 +23,7 @@ import org.gradle.api.Task;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.internal.java.JavaLibrary;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
 import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet;
@@ -68,6 +69,7 @@ public class JavaPlugin implements Plugin<Project> {
 
         configureSourceSets(javaConvention);
         configureConfigurations(project);
+        configureComponent(project);
 
         configureJavaDoc(javaConvention);
         configureTest(project, javaConvention);
@@ -160,6 +162,11 @@ public class JavaPlugin implements Plugin<Project> {
         configurations.getByName(Dependency.DEFAULT_CONFIGURATION).extendsFrom(runtimeConfiguration);
     }
 
+
+    private void configureComponent(Project project) {
+        project.getComponents().add(new JavaLibrary(project.getConfigurations().getByName(RUNTIME_CONFIGURATION_NAME)));
+    }
+
     /**
      * Adds a dependency on tasks with the specified name in other projects.  The other projects are determined from
      * project lib dependencies using the specified configuration name. These may be projects this project depends on or
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/GitHubDependenciesPlugin.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/GitHubDependenciesPlugin.groovy
deleted file mode 100644
index 3ac4c3b..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/GitHubDependenciesPlugin.groovy
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.plugins.github
-
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.gradle.api.artifacts.repositories.MavenArtifactRepository
-import org.gradle.api.internal.artifacts.BaseRepositoryFactory
-import org.gradle.api.internal.artifacts.repositories.DefaultPasswordCredentials
-import org.gradle.api.internal.file.FileResolver
-import org.gradle.api.plugins.github.internal.DefaultGitHubDownloadsRepository
-import org.gradle.internal.Factory
-import org.gradle.internal.reflect.Instantiator
-
-import javax.inject.Inject
-import org.gradle.api.internal.artifacts.DependencyResolutionServices
-import org.gradle.api.Incubating
-
- at Incubating
-class GitHubDependenciesPlugin implements Plugin<Project> {
-
-    FileResolver fileResolver
-    Instantiator instantiator
-    BaseRepositoryFactory resolverFactory
-
-    @Inject
-    GitHubDependenciesPlugin(FileResolver fileResolver, Instantiator instantiator, DependencyResolutionServices dependencyResolutionServices) {
-        this.fileResolver = fileResolver
-        this.instantiator = instantiator
-        this.resolverFactory = dependencyResolutionServices.baseRepositoryFactory
-    }
-
-    void apply(Project project) {
-        Factory<MavenArtifactRepository> mavenRepostoryFactory = new Factory<MavenArtifactRepository>() {
-            MavenArtifactRepository create() {
-                resolverFactory.createMavenRepository()
-            }
-        }
-
-        Factory<GitHubDownloadsRepository> downloadsRepositoryFactory = new Factory<GitHubDownloadsRepository>() {
-            GitHubDownloadsRepository create() {
-                def passwordCredentials = instantiator.newInstance(DefaultPasswordCredentials)
-                instantiator.newInstance(DefaultGitHubDownloadsRepository, fileResolver, passwordCredentials, mavenRepostoryFactory)
-            }
-        }
-
-        project.repositories.extensions.create("github", GitHubRepositoryHandlerExtension, project.repositories, downloadsRepositoryFactory)
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/GitHubDownloadsRepository.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/GitHubDownloadsRepository.java
deleted file mode 100644
index c413ae1..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/GitHubDownloadsRepository.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.plugins.github;
-
-import org.gradle.api.Incubating;
-import org.gradle.api.artifacts.repositories.ArtifactRepository;
-import org.gradle.api.artifacts.repositories.AuthenticationSupported;
-
-import java.net.URI;
-
-/**
- * A dependency repository that uses GitHub downloads as the source.
- *
- * A value for {@link #setUser(String)} must be provided before this can be used.
- * <p>
- * Given the following repository definition:
- * <pre>
- * repositories {
- *     github.downloads {
- *       user = "githubUser"
- *     }
- * }</pre>
- * <p>
- * The following dependency notations will resolve to:
- * <ul>
- * <li>{@code myProject:myThing} - {@code https://github.com/downloads/githubUser/myProject/myThing.jar}
- * <li>{@code myProject:myThing:1.0} - {@code https://github.com/downloads/githubUser/myProject/myThing-1.0.jar}
- * <li>{@code myProject:myThing at zip} - {@code https://github.com/downloads/githubUser/myProject/myThing.zip}
- * </ul>
- */
- at Incubating
-public interface GitHubDownloadsRepository extends ArtifactRepository, AuthenticationSupported {
-
-    /**
-     * {@value #DOWNLOADS_URL_BASE}
-     */
-    String DOWNLOADS_URL_BASE = "https://github.com/downloads";
-
-    /**
-     * Override the default base url of '{@value #DOWNLOADS_URL_BASE}'
-     *
-     * @param baseUrl The new base url
-     */
-    void setBaseUrl(Object baseUrl);
-
-    /**
-     * The base GitHub downloads url.
-     *
-     * Defaults to '{@value #DOWNLOADS_URL_BASE}'
-     *
-     * @return The base GitHub downloads url.
-     */
-    URI getBaseUrl();
-
-    /**
-     * Sets the GitHub user/organisation name that houses the downloads.
-     *
-     * Given a GitHub Downloads URL, this is the value at {@value #DOWNLOADS_URL_BASE}/«user».
-     *
-     * @param user The GitHub user/organisation name that houses the downloads.
-     */
-    void setUser(String user);
-
-    /**
-     * The GitHub user/organisation name that houses the downloads.
-     *
-     * @return The GitHub user/organisation name that houses the downloads.
-     */
-    String getUser();
-
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/GitHubRepositoryHandlerExtension.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/GitHubRepositoryHandlerExtension.java
deleted file mode 100644
index af6dfed..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/GitHubRepositoryHandlerExtension.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.plugins.github;
-
-import org.gradle.api.Action;
-import org.gradle.api.Incubating;
-import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.internal.Factory;
-
-/**
- * Provides GitHub oriented dependency repository notations.
- */
- at Incubating
-public class GitHubRepositoryHandlerExtension {
-
-    private final RepositoryHandler repositories;
-    private final Factory<GitHubDownloadsRepository> downloadsRepositoryFactory;
-
-    public GitHubRepositoryHandlerExtension(RepositoryHandler repositories, Factory<GitHubDownloadsRepository> downloadsRepositoryFactory) {
-        this.repositories = repositories;
-        this.downloadsRepositoryFactory = downloadsRepositoryFactory;
-    }
-
-    public GitHubDownloadsRepository downloads(final String user) {
-        return downloads(new Action<GitHubDownloadsRepository>() {
-            public void execute(GitHubDownloadsRepository gitHubDownloadsRepository) {
-                gitHubDownloadsRepository.setUser(user);
-            }
-        });
-    }
-
-    public GitHubDownloadsRepository downloads(Action<GitHubDownloadsRepository> configure) {
-        GitHubDownloadsRepository repository = downloadsRepositoryFactory.create();
-        configure.execute(repository);
-        repositories.add(repository);
-        return repository;
-    }
-
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/internal/DefaultGitHubDownloadsRepository.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/internal/DefaultGitHubDownloadsRepository.java
deleted file mode 100644
index 40bb38f..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/internal/DefaultGitHubDownloadsRepository.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.plugins.github.internal;
-
-import groovy.lang.Closure;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.artifacts.repositories.ArtifactRepository;
-import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.api.artifacts.repositories.PasswordCredentials;
-import org.gradle.api.internal.artifacts.repositories.AbstractArtifactRepository;
-import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
-import org.gradle.api.internal.artifacts.repositories.resolver.MavenResolver;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.plugins.github.GitHubDownloadsRepository;
-import org.gradle.internal.Factory;
-import org.gradle.util.ConfigureUtil;
-
-import java.net.URI;
-
-public class DefaultGitHubDownloadsRepository extends AbstractArtifactRepository implements GitHubDownloadsRepository, ArtifactRepositoryInternal {
-
-    private static final String PATTERN = "[organisation]/[artifact](-[revision])(-[classifier]).[ext]";
-
-    private String user;
-    private Object baseUrl = DOWNLOADS_URL_BASE;
-
-    private final PasswordCredentials credentials;
-    private final FileResolver fileResolver;
-    private final Factory<MavenArtifactRepository> repositoryFactory;
-
-    public DefaultGitHubDownloadsRepository(FileResolver fileResolver, PasswordCredentials credentials, Factory<MavenArtifactRepository> repositoryFactory) {
-        this.fileResolver = fileResolver;
-        this.credentials = credentials;
-        this.repositoryFactory = repositoryFactory;
-    }
-
-    public String getUser() {
-        return user;
-    }
-
-    public void setUser(String user) {
-        this.user = user;
-    }
-
-    public URI getBaseUrl() {
-        return fileResolver.resolveUri(baseUrl);
-    }
-
-    public void setBaseUrl(Object baseUrl) {
-        this.baseUrl = baseUrl;
-    }
-
-    public String getName() {
-        if (super.getName() == null) {
-            return getDefaultName();
-        } else {
-            return super.getName();
-        }
-    }
-
-    private String getDefaultName() {
-        String user = getUser();
-        return String.format("GitHub Downloads for GitHub user '%s'", user == null ? "" : user);
-    }
-
-
-    public PasswordCredentials getCredentials() {
-        return credentials;
-    }
-
-    public void credentials(Closure closure) {
-        ConfigureUtil.configure(closure, credentials);
-    }
-
-    public DependencyResolver createResolver() {
-        MavenArtifactRepository repository = repositoryFactory.create();
-        repository.setUrl(getEffectiveRepoUrl());
-        repository.setName(getName());
-        applyCredentialsTo(repository.getCredentials());
-
-        ArtifactRepositoryInternal repositoryInternal = toArtifactRepositoryInternal(repository);
-        MavenResolver resolver = toMavenResolver(repositoryInternal.createResolver());
-
-        resolver.setPattern(PATTERN);
-        return resolver;
-    }
-
-    private ArtifactRepositoryInternal toArtifactRepositoryInternal(ArtifactRepository artifactRepository) {
-        return (ArtifactRepositoryInternal) artifactRepository;
-    }
-
-    private MavenResolver toMavenResolver(DependencyResolver dependencyResolver) {
-        return (MavenResolver) dependencyResolver;
-    }
-
-    private URI getEffectiveRepoUrl() {
-        return fileResolver.resolveUri(String.format("%s/%s", getBaseUrl().toString(), getUser()));
-    }
-
-    private void applyCredentialsTo(PasswordCredentials other) {
-        other.setUsername(credentials.getUsername());
-        other.setPassword(credentials.getPassword());
-    }
-
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/application/CreateStartScripts.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/application/CreateStartScripts.groovy
index b3b9348..2b503b6 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/application/CreateStartScripts.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/application/CreateStartScripts.groovy
@@ -29,7 +29,7 @@ import org.gradle.util.GUtil
  *
  * @author Rene Groeschke
  */
-class CreateStartScripts extends ConventionTask {
+public class CreateStartScripts extends ConventionTask {
 
     /**
      * The directory to write the scripts into.
@@ -95,8 +95,6 @@ class CreateStartScripts extends ConventionTask {
 
     @TaskAction
     void generate() {
-        getOutputDir().mkdirs()
-
         def generator = new StartScriptGenerator()
         generator.applicationName = getApplicationName()
         generator.mainClassName = getMainClassName()
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompile.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompile.java
index e2aeca4..1e36dc5 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompile.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompile.java
@@ -29,11 +29,9 @@ import org.gradle.api.tasks.InputFiles;
 import org.gradle.api.tasks.Nested;
 import org.gradle.api.tasks.WorkResult;
 import org.gradle.internal.Factory;
+import org.gradle.util.GFileUtils;
 
 import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
 
 /**
  * Compiles Groovy source files, and optionally, Java source files.
@@ -61,29 +59,28 @@ public class GroovyCompile extends AbstractCompile {
     }
 
     protected void compile() {
-        List<File> taskClasspath = new ArrayList<File>(getGroovyClasspath().getFiles());
-        throwExceptionIfTaskClasspathIsEmpty(taskClasspath);
+        checkGroovyClasspathIsNonEmpty();
         DefaultGroovyJavaJointCompileSpec spec = new DefaultGroovyJavaJointCompileSpec();
         spec.setSource(getSource());
         spec.setDestinationDir(getDestinationDir());
         spec.setClasspath(getClasspath());
         spec.setSourceCompatibility(getSourceCompatibility());
         spec.setTargetCompatibility(getTargetCompatibility());
-        spec.setGroovyClasspath(taskClasspath);
+        spec.setGroovyClasspath(getGroovyClasspath());
         spec.setCompileOptions(compileOptions);
         spec.setGroovyCompileOptions(groovyCompileOptions);
         if (spec.getGroovyCompileOptions().getStubDir() == null) {
             File dir = tempFileProvider.newTemporaryFile("groovy-java-stubs");
-            dir.mkdirs();
+            GFileUtils.mkdirs(dir);
             spec.getGroovyCompileOptions().setStubDir(dir);
         }
         WorkResult result = compiler.execute(spec);
         setDidWork(result.getDidWork());
     }
 
-    private void throwExceptionIfTaskClasspathIsEmpty(Collection<File> taskClasspath) {
-        if (taskClasspath.size() == 0) {
-            throw new InvalidUserDataException("You must assign a Groovy library to the 'groovy' configuration.");
+    private void checkGroovyClasspathIsNonEmpty() {
+        if (getGroovyClasspath().isEmpty()) {
+            throw new InvalidUserDataException("'" + getName() + ".groovyClasspath' must not be empty. It is either configured automatically by the 'groovy-base' plugin, or can be set manually.");
         }
     }
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/Test.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/Test.java
index 4b414db..2a4aa96 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/Test.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/Test.java
@@ -18,6 +18,7 @@ package org.gradle.api.tasks.testing;
 
 import groovy.lang.Closure;
 import org.gradle.api.GradleException;
+import org.gradle.api.Incubating;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
 import org.gradle.api.file.FileTreeElement;
@@ -28,8 +29,12 @@ import org.gradle.api.internal.tasks.testing.TestResultProcessor;
 import org.gradle.api.internal.tasks.testing.detection.DefaultTestExecuter;
 import org.gradle.api.internal.tasks.testing.detection.TestExecuter;
 import org.gradle.api.internal.tasks.testing.junit.JUnitTestFramework;
+import org.gradle.api.internal.tasks.testing.junit.report.DefaultTestReport;
+import org.gradle.api.internal.tasks.testing.junit.report.TestReporter;
+import org.gradle.api.internal.tasks.testing.junit.result.Binary2JUnitXmlReportGenerator;
+import org.gradle.api.internal.tasks.testing.junit.result.TestReportDataCollector;
 import org.gradle.api.internal.tasks.testing.logging.*;
-import org.gradle.api.internal.tasks.testing.results.*;
+import org.gradle.api.internal.tasks.testing.results.TestListenerAdapter;
 import org.gradle.api.internal.tasks.testing.testng.TestNGTestFramework;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.specs.Spec;
@@ -38,7 +43,9 @@ import org.gradle.api.tasks.testing.logging.TestLogging;
 import org.gradle.api.tasks.testing.logging.TestLoggingContainer;
 import org.gradle.api.tasks.util.PatternFilterable;
 import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.internal.Factory;
 import org.gradle.internal.reflect.Instantiator;
+import org.gradle.listener.ClosureBackedMethodInvocationDispatch;
 import org.gradle.listener.ListenerBroadcast;
 import org.gradle.listener.ListenerManager;
 import org.gradle.logging.ConsoleRenderer;
@@ -51,37 +58,49 @@ import org.gradle.process.internal.DefaultJavaForkOptions;
 import org.gradle.process.internal.WorkerProcessBuilder;
 import org.gradle.util.ConfigureUtil;
 
+import javax.inject.Inject;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import static java.util.Arrays.asList;
+
 /**
- * Executes tests. Supports JUnit (3.8.x or 4.x) or TestNG tests.
- * <p>
- * An example with a blend of various settings
+ * Executes JUnit (3.8.x or 4.x) or TestNG tests. Test are always run in (one or more) separate JVMs.
+ * The sample below shows various configuration options.
+ *
  * <pre autoTested=''>
  * apply plugin: 'java' // adds 'test' task
  *
  * test {
- *   //configuring a system property for tests
+ *   // enable TestNG support (default is JUnit)
+ *   useTestNG()
+ *
+ *   // set a system property for the test JVM(s)
  *   systemProperty 'some.prop', 'value'
  *
- *   //tuning the included/excluded tests
+ *   // explicitly include or exclude tests
  *   include 'org/foo/**'
  *   exclude 'org/boo/**'
  *
- *   //makes the standard streams (err and out) visible at console when running tests
+ *   // show standard out and standard error of the test JVM(s) on the console
  *   testLogging.showStandardStreams = true
  *
- *   //tweaking memory settings for the forked vm that runs tests
- *   jvmArgs '-Xms128m', '-Xmx512m', '-XX:MaxPermSize=128m'
+ *   // set heap size for the test JVM(s)
+ *   minHeapSize = "128m"
+ *   maxHeapSize = "512m"
+ *
+ *   // set JVM arguments for the test JVM(s)
+ *   jvmArgs '-XX:MaxPermSize=256m'
  *
- *   //listening to test execution events
+ *   // listen to events in the test execution lifecycle
  *   beforeTest { descriptor ->
  *      logger.lifecycle("Running test: " + descriptor)
  *   }
+ *
+ *   // listen to standard out and standard error of the test JVM(s)
  *   onOutput { descriptor, event ->
  *      logger.lifecycle("Test: " + descriptor + " produced standard out/err: " + event.message )
  *   }
@@ -91,9 +110,11 @@ import java.util.Set;
  * @author Hans Dockter
  */
 public class Test extends ConventionTask implements JavaForkOptions, PatternFilterable, VerificationTask {
+
     private final ListenerBroadcast<TestListener> testListenerBroadcaster;
     private final ListenerBroadcast<TestOutputListener> testOutputListenerBroadcaster;
     private final StyledTextOutputFactory textOutputFactory;
+    private final ProgressLoggerFactory progressLoggerFactory;
     private final TestLoggingContainer testLogging;
     private final DefaultJavaForkOptions options;
 
@@ -101,28 +122,39 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     private List<File> testSrcDirs = new ArrayList<File>();
     private File testClassesDir;
     private File testResultsDir;
+    private File binResultsDir;
     private File testReportDir;
     private PatternFilterable patternSet = new PatternSet();
     private boolean ignoreFailures;
     private FileCollection classpath;
     private TestFramework testFramework;
-    private boolean testReport;
+    private boolean testReport = true;
     private boolean scanForTestClasses = true;
     private long forkEvery;
     private int maxParallelForks = 1;
-
-    public Test() {
-        testListenerBroadcaster = getServices().get(ListenerManager.class).createAnonymousBroadcaster(TestListener.class);
-        testOutputListenerBroadcaster = getServices().get(ListenerManager.class).createAnonymousBroadcaster(TestOutputListener.class);
-        textOutputFactory = getServices().get(StyledTextOutputFactory.class);
-        options = new DefaultJavaForkOptions(getServices().get(FileResolver.class));
+    private TestReporter testReporter;
+
+    @Inject
+    public Test(ListenerManager listenerManager, StyledTextOutputFactory textOutputFactory, FileResolver fileResolver,
+                Factory<WorkerProcessBuilder> processBuilderFactory, ActorFactory actorFactory, Instantiator instantiator,
+                ProgressLoggerFactory progressLoggerFactory) {
+        this.progressLoggerFactory = progressLoggerFactory;
+        testListenerBroadcaster = listenerManager.createAnonymousBroadcaster(TestListener.class);
+        testOutputListenerBroadcaster = listenerManager.createAnonymousBroadcaster(TestOutputListener.class);
+        this.textOutputFactory = textOutputFactory;
+        options = new DefaultJavaForkOptions(fileResolver);
         options.setEnableAssertions(true);
-        testExecuter = new DefaultTestExecuter(getServices().getFactory(WorkerProcessBuilder.class), getServices().get(ActorFactory.class));
-
-        Instantiator instantiator = getServices().get(Instantiator.class);
+        testExecuter = new DefaultTestExecuter(processBuilderFactory, actorFactory);
         testLogging = instantiator.newInstance(DefaultTestLoggingContainer.class, instantiator);
+        testReporter = new DefaultTestReport();
     }
 
+    /**
+     * ATM. for testing only
+     * */
+    void setTestReporter(TestReporter testReporter){
+        this.testReporter = testReporter;
+    }
     void setTestExecuter(TestExecuter testExecuter) {
         this.testExecuter = testExecuter;
     }
@@ -398,15 +430,36 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
         addTestListener(eventLogger);
         addTestOutputListener(eventLogger);
 
-        ProgressLoggerFactory progressLoggerFactory = getServices().get(ProgressLoggerFactory.class);
+        File binaryResultsDir = getBinResultsDir();
+        getProject().delete(binaryResultsDir);
+        getProject().mkdir(binaryResultsDir);
+
+        TestReportDataCollector testReportDataCollector = new TestReportDataCollector(binaryResultsDir);
+        addTestListener(testReportDataCollector);
+        addTestOutputListener(testReportDataCollector);
+
         TestCountLogger testCountLogger = new TestCountLogger(progressLoggerFactory);
         addTestListener(testCountLogger);
 
         TestResultProcessor resultProcessor = new TestListenerAdapter(
                 getTestListenerBroadcaster().getSource(), testOutputListenerBroadcaster.getSource());
 
-        testExecuter.execute(this, resultProcessor);
-        testFramework.report();
+        try {
+            testExecuter.execute(this, resultProcessor);
+        } finally {
+            testListenerBroadcaster.removeAll(asList(eventLogger, testReportDataCollector, testCountLogger));
+            testOutputListenerBroadcaster.removeAll(asList(eventLogger, testReportDataCollector));
+        }
+
+        Binary2JUnitXmlReportGenerator binary2JUnitXmlReportGenerator = new Binary2JUnitXmlReportGenerator(getTestResultsDir(), testReportDataCollector);
+        binary2JUnitXmlReportGenerator.generate();
+
+        if (!isTestReport()) {
+            getLogger().info("Test report disabled, omitting generation of the HTML test report.");
+        } else {
+            testReporter.generateReport(testReportDataCollector, getTestReportDir());
+        }
+
         testFramework = null;
 
         if (testCountLogger.hadFailures()) {
@@ -414,21 +467,18 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
         }
     }
 
+
     /**
-     * Returns the {@link org.gradle.api.tasks.testing.TestListener} broadcaster.  This broadcaster will send messages
-     * to all listeners that have been registered with the ListenerManager.
+     * Returns the {@link org.gradle.api.tasks.testing.TestListener} broadcaster.  This broadcaster will send messages to all listeners that have been registered with the ListenerManager.
      */
     ListenerBroadcast<TestListener> getTestListenerBroadcaster() {
         return testListenerBroadcaster;
     }
 
     /**
-     * Registers a test listener with this task. Consider also the following handy methods for quicker hooking into test execution:
-     * {@link #beforeTest(groovy.lang.Closure)}, {@link #afterTest(groovy.lang.Closure)},
-     * {@link #beforeSuite(groovy.lang.Closure)}, {@link #afterSuite(groovy.lang.Closure)}
-     * <p>
-     * This listener will NOT be notified of tests executed by other tasks.
-     * To get that behavior, use {@link org.gradle.api.invocation.Gradle#addListener(Object)}.
+     * Registers a test listener with this task. Consider also the following handy methods for quicker hooking into test execution: {@link #beforeTest(groovy.lang.Closure)}, {@link
+     * #afterTest(groovy.lang.Closure)}, {@link #beforeSuite(groovy.lang.Closure)}, {@link #afterSuite(groovy.lang.Closure)} <p> This listener will NOT be notified of tests executed by other tasks. To
+     * get that behavior, use {@link org.gradle.api.invocation.Gradle#addListener(Object)}.
      *
      * @param listener The listener to add.
      */
@@ -446,10 +496,9 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
-     * Unregisters a test listener with this task.  This method will only remove listeners that were added by calling
-     * {@link #addTestListener(org.gradle.api.tasks.testing.TestListener)} on this task.  If the listener was registered
-     * with Gradle using {@link org.gradle.api.invocation.Gradle#addListener(Object)} this method will not do anything.
-     * Instead, use {@link org.gradle.api.invocation.Gradle#removeListener(Object)}.
+     * Unregisters a test listener with this task.  This method will only remove listeners that were added by calling {@link #addTestListener(org.gradle.api.tasks.testing.TestListener)} on this task.
+     * If the listener was registered with Gradle using {@link org.gradle.api.invocation.Gradle#addListener(Object)} this method will not do anything. Instead, use {@link
+     * org.gradle.api.invocation.Gradle#removeListener(Object)}.
      *
      * @param listener The listener to remove.
      */
@@ -458,10 +507,9 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
-     * Unregisters a test output listener with this task.  This method will only remove listeners that were added by calling
-     * {@link #addTestOutputListener(org.gradle.api.tasks.testing.TestOutputListener)} on this task.  If the listener was registered
-     * with Gradle using {@link org.gradle.api.invocation.Gradle#addListener(Object)} this method will not do anything.
-     * Instead, use {@link org.gradle.api.invocation.Gradle#removeListener(Object)}.
+     * Unregisters a test output listener with this task.  This method will only remove listeners that were added by calling {@link #addTestOutputListener(org.gradle.api.tasks.testing.TestOutputListener)}
+     * on this task.  If the listener was registered with Gradle using {@link org.gradle.api.invocation.Gradle#addListener(Object)} this method will not do anything. Instead, use {@link
+     * org.gradle.api.invocation.Gradle#removeListener(Object)}.
      *
      * @param listener The listener to remove.
      */
@@ -470,71 +518,56 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
-     * <p>Adds a closure to be notified before a test suite is executed. A {@link org.gradle.api.tasks.testing.TestDescriptor}
-     * instance is passed to the closure as a parameter.</p>
+     * <p>Adds a closure to be notified before a test suite is executed. A {@link org.gradle.api.tasks.testing.TestDescriptor} instance is passed to the closure as a parameter.</p>
      *
-     * <p>This method is also called before any test suites are executed. The provided descriptor will have a null
-     * parent suite.</p>
+     * <p>This method is also called before any test suites are executed. The provided descriptor will have a null parent suite.</p>
      *
      * @param closure The closure to call.
      */
     public void beforeSuite(Closure closure) {
-        testListenerBroadcaster.add("beforeSuite", closure);
+        testListenerBroadcaster.add(new ClosureBackedMethodInvocationDispatch("beforeSuite", closure));
     }
 
     /**
-     * <p>Adds a closure to be notified after a test suite has executed. A {@link org.gradle.api.tasks.testing.TestDescriptor}
-     * and {@link TestResult} instance are passed to the closure as a parameter.</p>
+     * <p>Adds a closure to be notified after a test suite has executed. A {@link org.gradle.api.tasks.testing.TestDescriptor} and {@link TestResult} instance are passed to the closure as a
+     * parameter.</p>
      *
-     * <p>This method is also called after all test suites are executed. The provided descriptor will have a null parent
-     * suite.</p>
+     * <p>This method is also called after all test suites are executed. The provided descriptor will have a null parent suite.</p>
      *
      * @param closure The closure to call.
      */
     public void afterSuite(Closure closure) {
-        testListenerBroadcaster.add("afterSuite", closure);
+        testListenerBroadcaster.add(new ClosureBackedMethodInvocationDispatch("afterSuite", closure));
     }
 
     /**
-     * Adds a closure to be notified before a test is executed. A {@link org.gradle.api.tasks.testing.TestDescriptor}
-     * instance is passed to the closure as a parameter.
+     * Adds a closure to be notified before a test is executed. A {@link org.gradle.api.tasks.testing.TestDescriptor} instance is passed to the closure as a parameter.
      *
      * @param closure The closure to call.
      */
     public void beforeTest(Closure closure) {
-        testListenerBroadcaster.add("beforeTest", closure);
+        testListenerBroadcaster.add(new ClosureBackedMethodInvocationDispatch("beforeTest", closure));
     }
 
     /**
-     * Adds a closure to be notified after a test has executed. A {@link org.gradle.api.tasks.testing.TestDescriptor}
-     * and {@link TestResult} instance are passed to the closure as a parameter.
+     * Adds a closure to be notified after a test has executed. A {@link org.gradle.api.tasks.testing.TestDescriptor} and {@link TestResult} instance are passed to the closure as a parameter.
      *
      * @param closure The closure to call.
      */
     public void afterTest(Closure closure) {
-        testListenerBroadcaster.add("afterTest", closure);
+        testListenerBroadcaster.add(new ClosureBackedMethodInvocationDispatch("afterTest", closure));
     }
 
     /**
-     * Adds a closure to be notified when output from the test received.
-     * A {@link org.gradle.api.tasks.testing.TestDescriptor}
-     * and {@link org.gradle.api.tasks.testing.TestOutputEvent} instance are passed to the closure as a parameter.
-     * <pre autoTested=''>
-     * apply plugin: 'java'
+     * Adds a closure to be notified when output from the test received. A {@link org.gradle.api.tasks.testing.TestDescriptor} and {@link org.gradle.api.tasks.testing.TestOutputEvent} instance are
+     * passed to the closure as a parameter. <pre autoTested=''> apply plugin: 'java'
      *
-     * test {
-     *   onOutput { descriptor, event ->
-     *     if (event.destination == TestOutputEvent.Destination.StdErr) {
-     *       logger.error("Test: " + descriptor + ", error: " + event.message)
-     *     }
-     *   }
-     * }
-     * </pre>
+     * test { onOutput { descriptor, event -> if (event.destination == TestOutputEvent.Destination.StdErr) { logger.error("Test: " + descriptor + ", error: " + event.message) } } } </pre>
      *
      * @param closure The closure to call.
      */
     public void onOutput(Closure closure) {
-        testOutputListenerBroadcaster.add("onOutput", closure);
+        testOutputListenerBroadcaster.add(new ClosureBackedMethodInvocationDispatch("onOutput", closure));
     }
 
     /**
@@ -628,9 +661,9 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
-     * Returns the root folder for the test results.
+     * Returns the root folder for the test results in XML format.
      *
-     * @return the test result directory, containing the internal test results, mostly in xml form.
+     * @return the test result directory, containing the test results in XML format.
      */
     @OutputDirectory
     public File getTestResultsDir() {
@@ -647,6 +680,26 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
+     * Returns the root folder for the test results in internal binary format.
+     *
+     * @return the test result directory, containing the test results in binary format.
+     */
+    @OutputDirectory @Incubating
+    public File getBinResultsDir() {
+        return binResultsDir;
+    }
+
+    /**
+     * Sets the root folder for the test results in internal binary format.
+     *
+     * @param binResultsDir The root folder
+     */
+    @Incubating
+    public void setBinResultsDir(File binResultsDir) {
+        this.binResultsDir = binResultsDir;
+    }
+
+    /**
      * Returns the root folder for the test reports.
      *
      * @return the test report directory, containing the test report mostly in HTML form.
@@ -781,8 +834,7 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     /**
      * Specifies that JUnit should be used to execute the tests.
      *
-     * @param testFrameworkConfigure A closure used to configure the JUnit options. This closure is passed an instance
-     * of type {@link org.gradle.api.tasks.testing.junit.JUnitOptions}.
+     * @param testFrameworkConfigure A closure used to configure the JUnit options. This closure is passed an instance of type {@link org.gradle.api.tasks.testing.junit.JUnitOptions}.
      */
     public void useJUnit(Closure testFrameworkConfigure) {
         useTestFramework(new JUnitTestFramework(this), testFrameworkConfigure);
@@ -798,8 +850,7 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     /**
      * Specifies that TestNG should be used to execute the tests.
      *
-     * @param testFrameworkConfigure A closure used to configure the TestNG options. This closure is passed an instance
-     * of type {@link org.gradle.api.tasks.testing.testng.TestNGOptions}.
+     * @param testFrameworkConfigure A closure used to configure the TestNG options. This closure is passed an instance of type {@link org.gradle.api.tasks.testing.testng.TestNGOptions}.
      */
     public void useTestNG(Closure testFrameworkConfigure) {
         useTestFramework(new TestNGTestFramework(this), testFrameworkConfigure);
@@ -818,22 +869,7 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
-     * Specifies whether the test report should be generated.
-     * <p>
-     * Since Gradle 1.3 the TestNG uses this property in the following way:
-     * <ul>
-     *     <li>report 'on' means that new TestNG reporting is used:
-     *     new improved html reports are generated, old reports are not generated,
-     *     xml junit results (typically consumed by your CI server) are generated to {@link Test#getTestResultsDir()} dir
-     *     (previously those were generated into {@code Test.testReportsDir/junitreports} dir).
-     *     </li>
-     *     <li>report 'off' means that old TestNG reporting is used: old html reports are generated,
-     *     the xml junit results (for CI) are generated to {@code Test.testReportsDir/junitreports} dir.
-     *     </li>
-     * </ul>
-     * Even though the property is processed differently by TestNG tests, the default behavior does not change.
-     * The changes above were needed so that we can improve the TestNG reporting in a backwards compatible way.
-     * For more information please refer to the release notes for Gradle 1.3.
+     * Specifies whether the test HTML report should be generated.
      */
     @Input
     public boolean isTestReport() {
@@ -865,9 +901,8 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
-     * Specifies whether test classes should be detected. When {@code true} the classes which match the include and
-     * exclude patterns are scanned for test classes, and any found are executed. When {@code false} the classes which
-     * match the include and exclude patterns are executed.
+     * Specifies whether test classes should be detected. When {@code true} the classes which match the include and exclude patterns are scanned for test classes, and any found are executed. When
+     * {@code false} the classes which match the include and exclude patterns are executed.
      */
     @Input
     public boolean isScanForTestClasses() {
@@ -879,8 +914,7 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
-     * Returns the maximum number of test classes to execute in a forked test process. The forked test process will be
-     * restarted when this limit is reached. The default value is 0 (no maximum).
+     * Returns the maximum number of test classes to execute in a forked test process. The forked test process will be restarted when this limit is reached. The default value is 0 (no maximum).
      *
      * @return The maximum number of test classes. Returns 0 when there is no maximum.
      */
@@ -901,8 +935,7 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
-     * Returns the maximum number of forked test processes to execute in parallel. The default value is 1 (no parallel
-     * test execution).
+     * Returns the maximum number of forked test processes to execute in parallel. The default value is 1 (no parallel test execution).
      *
      * @return The maximum number of forked test processes.
      */
@@ -911,8 +944,7 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
-     * Sets the maximum number of forked test processes to execute in parallel. Set to 1 to disable parallel test
-     * execution.
+     * Sets the maximum number of forked test processes to execute in parallel. Set to 1 to disable parallel test execution.
      *
      * @param maxParallelForks The maximum number of forked test processes.
      */
@@ -935,16 +967,11 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
-     * Allows to set options related to which test events are logged to the console, and on which detail
-     * level. For example, to show more information about exceptions use:
+     * Allows to set options related to which test events are logged to the console, and on which detail level. For example, to show more information about exceptions use:
      *
-     * <pre autoTested=''>
-     * apply plugin: 'java'
+     * <pre autoTested=''> apply plugin: 'java'
      *
-     * test.testLogging {
-     *     exceptionFormat "full"
-     * }
-     * </pre>
+     * test.testLogging { exceptionFormat "full" } </pre>
      *
      * For further information see {@link TestLoggingContainer}.
      *
@@ -955,15 +982,10 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
-     * Allows configuring the logging of the test execution, for example log eagerly the standard output, etc.
-     * <pre autoTested=''>
-     * apply plugin: 'java'
+     * Allows configuring the logging of the test execution, for example log eagerly the standard output, etc. <pre autoTested=''> apply plugin: 'java'
+     *
+     * //makes the standard streams (err and out) visible at console when running tests test.testLogging { showStandardStreams = true } </pre>
      *
-     * //makes the standard streams (err and out) visible at console when running tests
-     * test.testLogging {
-     *   showStandardStreams = true
-     * }
-     * </pre>
      * @param closure configure closure
      */
     public void testLogging(Closure closure) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestReport.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestReport.java
new file mode 100644
index 0000000..e4c65d4
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestReport.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks.testing;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Incubating;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.UnionFileCollection;
+import org.gradle.api.internal.tasks.testing.junit.report.DefaultTestReport;
+import org.gradle.api.internal.tasks.testing.junit.result.AggregateTestResultsProvider;
+import org.gradle.api.internal.tasks.testing.junit.result.TestResultsProvider;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.SkipWhenEmpty;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Generates an HTML test report from the results of one or more {@link Test} tasks.
+ */
+ at Incubating
+public class TestReport extends DefaultTask {
+    private File destinationDir;
+    private List<Object> results = new ArrayList<Object>();
+
+    /**
+     * Returns the directory to write the HTML report to.
+     */
+    @OutputDirectory
+    public File getDestinationDir() {
+        return destinationDir;
+    }
+
+    /**
+     * Sets the directory to write the HTML report to.
+     */
+    public void setDestinationDir(File destinationDir) {
+        this.destinationDir = destinationDir;
+    }
+
+    /**
+     * Returns the set of binary test results to include in the report.
+     */
+    @InputFiles @SkipWhenEmpty
+    public FileCollection getTestResultDirs() {
+        UnionFileCollection dirs = new UnionFileCollection();
+        for (Object result : results) {
+            addTo(result, dirs);
+        }
+        return dirs;
+    }
+
+    private void addTo(Object result, UnionFileCollection dirs) {
+        if (result instanceof Test) {
+            Test test = (Test) result;
+            dirs.add(getProject().files(test.getBinResultsDir()).builtBy(test));
+        } else if (result instanceof Iterable<?>) {
+            Iterable<?> iterable = (Iterable<?>) result;
+            for (Object nested : iterable) {
+                addTo(nested, dirs);
+            }
+        } else {
+            dirs.add(getProject().files(result));
+        }
+    }
+
+    /**
+     * Sets the binary test results to use to include in the report. Each entry must point to a binary test results directory generated by a {@link Test}
+     * task.
+     */
+    public void setTestResultDirs(Iterable<File> testResultDirs) {
+        this.results.clear();
+        reportOn(testResultDirs);
+    }
+
+    /**
+     * Adds some results to include in the report.
+     *
+     * <p>This method accepts any parameter of the given types:
+     *
+     * <ul>
+     *
+     * <li>A {@link Test} task instance. The results from the test task are included in the report. The test task is automatically added
+     * as a dependency of this task.</li>
+     *
+     * <li>Anything that can be converted to a set of {@link File} instances as per {@link org.gradle.api.Project#files(Object...)}. These must
+     * point to the binary test results directory generated by a {@link Test} task instance.</li>
+     *
+     * <li>An {@link Iterable}. The contents of the iterable are converted recursively.</li>
+     *
+     * </ul>
+     *
+     * @param results The result objects.
+     */
+    public void reportOn(Object... results) {
+        for (Object result : results) {
+            this.results.add(result);
+        }
+    }
+
+    @TaskAction
+    void generateReport() {
+        TestResultsProvider resultsProvider = new AggregateTestResultsProvider(getTestResultDirs().getFiles());
+        DefaultTestReport testReport = new DefaultTestReport();
+        testReport.generateReport(resultsProvider, getDestinationDir());
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy
index 12a513f..7f59d9c 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy
@@ -57,8 +57,21 @@ public class TestNGOptions extends TestFrameworkOptions implements Serializable
     Set<String> excludeGroups = new HashSet<String>()
 
     /**
-     * The set of fully qualified classes that are TestNG listeners (for example org.testng.ITestListener or
-     * org.testng.IReporter)
+     * Fully qualified classes that are TestNG listeners (instances of org.testng.ITestListener or
+     * org.testng.IReporter). By default, the listeners set is empty.
+     *
+     * Configuring extra listener:
+     * <pre autoTested=''>
+     * apply plugin: 'java'
+     *
+     * test {
+     *   useTestNG() {
+     *     //creates emailable html file
+     *     //this reporter typically ships with TestNG library
+     *     listeners << 'org.testng.reporters.EmailableReporter'
+     *   }
+     * }
+     * </pre>
      */
     Set<String> listeners = new LinkedHashSet<String>()
 
@@ -78,10 +91,33 @@ public class TestNGOptions extends TestFrameworkOptions implements Serializable
 
     /**
      * Whether the default listeners and reporters should be used.
+     * Since Gradle 1.4 it defaults to 'false' so that Gradle can own the reports generation and provide various improvements.
+     * This option might be useful for advanced TestNG users who prefer the reports generated by the TestNG library.
+     * If you cannot live without some specific TestNG reporter please use {@link #listeners} property.
+     * If you really want to use all default TestNG reporters (e.g. generate the old reports):
+     *
+     * <pre autoTested=''>
+     * apply plugin: 'java'
+     *
+     * test {
+     *   useTestNG() {
+     *     //report generation delegated to TestNG library:
+     *     useDefaultListeners = true
+     *   }
+     *
+     *   //turn off Gradle's HTML report to avoid replacing the
+     *   //reports generated by TestNG library:
+     *   testReport = false
+     * }
+     * </pre>
+     *
+     * Please refer to the documentation of your version of TestNG what are the default listeners.
+     * At the moment of writing this documentation, the default listeners are a set of reporters that generate:
+     * TestNG variant of html results, TestNG variant of xml results in JUnit format, emailable html test report,
+     * xml results in TestNG format.
      *
-     * Defaults to true.
      */
-    boolean useDefaultListeners = true
+    boolean useDefaultListeners = false
 
     /**
      * Sets the default name of the test suite, if one is not specified in a suite xml file or in the source code.
@@ -102,7 +138,7 @@ public class TestNGOptions extends TestFrameworkOptions implements Serializable
 
     transient StringWriter suiteXmlWriter = null
     transient MarkupBuilder suiteXmlBuilder = null
-    private File projectDir
+    private final File projectDir
 
     TestNGOptions(File projectDir) {
         this.projectDir = projectDir
diff --git a/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/java-library-distribution.properties b/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/java-library-distribution.properties
new file mode 100755
index 0000000..b8ff58b
--- /dev/null
+++ b/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/java-library-distribution.properties
@@ -0,0 +1,17 @@
+#
+# Copyright 2012 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+implementation-class=org.gradle.api.plugins.JavaLibraryDistributionPlugin
\ No newline at end of file
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/plugins/GroovyJarFileTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/plugins/GroovyJarFileTest.groovy
new file mode 100644
index 0000000..8ebe2c4
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/plugins/GroovyJarFileTest.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.plugins
+
+import spock.lang.Specification
+
+class GroovyJarFileTest extends Specification {
+    def "parse non-Groovy file"() {
+        expect:
+        GroovyJarFile.parse(new File("groovy-other-2.0.5.jar")) == null
+        GroovyJarFile.parse(new File("groovy-2.0.5.zip")) == null
+    }
+
+    def "parse 'groovy' Jar"() {
+        def jar = GroovyJarFile.parse(new File("/lib/groovy-2.0.5.jar"))
+
+        expect:
+        jar != null
+        jar.file == new File("/lib/groovy-2.0.5.jar")
+        jar.baseName == "groovy"
+        jar.version.toString() == "2.0.5"
+        !jar.groovyAll
+        !jar.indy
+        jar.dependencyNotation == "org.codehaus.groovy:groovy:2.0.5"
+    }
+
+    def "parse 'groovy-all' Jar"() {
+        def jar = GroovyJarFile.parse(new File("/lib/groovy-all-2.0.5.jar"))
+
+        expect:
+        jar != null
+        jar.file == new File("/lib/groovy-all-2.0.5.jar")
+        jar.baseName == "groovy-all"
+        jar.version.toString() == "2.0.5"
+        jar.groovyAll
+        !jar.indy
+        jar.dependencyNotation == "org.codehaus.groovy:groovy-all:2.0.5"
+
+    }
+
+    def "parse indy Jar"() {
+        def jar = GroovyJarFile.parse(new File("/lib/groovy-2.0.5-indy.jar"))
+
+        expect:
+        jar != null
+        jar.file == new File("/lib/groovy-2.0.5-indy.jar")
+        jar.baseName == "groovy"
+        jar.version.toString() == "2.0.5"
+        !jar.groovyAll
+        jar.indy
+        jar.dependencyNotation == "org.codehaus.groovy:groovy:2.0.5:indy"
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompilerArgumentsGeneratorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompilerArgumentsGeneratorTest.groovy
index 1e1dcf7..e690afa 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompilerArgumentsGeneratorTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompilerArgumentsGeneratorTest.groovy
@@ -16,17 +16,16 @@
 
 package org.gradle.api.internal.tasks.compile
 
-import org.gradle.api.tasks.compile.CompileOptions
-import org.gradle.util.TemporaryFolder
+import com.google.common.collect.Lists
 import org.gradle.api.internal.file.TemporaryFileProvider
 import org.gradle.api.internal.file.collections.SimpleFileCollection
-import com.google.common.collect.Lists
-
+import org.gradle.api.tasks.compile.CompileOptions
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 class CommandLineJavaCompilerArgumentsGeneratorTest extends Specification {
-    @Rule TemporaryFolder tempDir
+    @Rule TestNameTestDirectoryProvider tempDir
     TemporaryFileProvider tempFileProvider = Mock()
     CommandLineJavaCompilerArgumentsGenerator argsGenerator = new CommandLineJavaCompilerArgumentsGenerator(tempFileProvider)
 
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/SimpleStaleClassCleanerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/SimpleStaleClassCleanerTest.groovy
index 9c12df6..9c7feb2 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/SimpleStaleClassCleanerTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/SimpleStaleClassCleanerTest.groovy
@@ -15,21 +15,21 @@
  */
 package org.gradle.api.internal.tasks.compile
 
-import spock.lang.Specification
-import org.junit.Rule
-import org.gradle.util.TemporaryFolder
-import org.gradle.api.internal.TaskOutputsInternal
 import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.TaskOutputsInternal
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
 
 class SimpleStaleClassCleanerTest extends Specification {
-    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     private final TaskOutputsInternal outputs = Mock()
     private final SimpleStaleClassCleaner cleaner = new SimpleStaleClassCleaner(outputs)
     
     def deletesAllPreviousOutputFiles() {
         def file1 = tmpDir.file('file1').createFile()
         def file2 = tmpDir.file('file2').createFile()
-        cleaner.destinationDir = tmpDir.dir
+        cleaner.destinationDir = tmpDir.testDirectory
 
         when:
         cleaner.execute()
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy
index 020d51e..db1e3e0 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy
@@ -15,43 +15,35 @@
  */
 package org.gradle.api.internal.tasks.testing.junit
 
+import junit.extensions.TestSetup
 import junit.framework.TestCase
-import org.gradle.api.internal.tasks.testing.TestCompleteEvent
-import org.gradle.api.internal.tasks.testing.TestDescriptorInternal
-import org.gradle.api.internal.tasks.testing.TestResultProcessor
-import org.gradle.api.internal.tasks.testing.TestStartEvent
-import org.gradle.api.internal.tasks.testing.TestClassRunInfo
-import org.gradle.util.JUnit4GroovyMockery
+import junit.framework.TestSuite
+import org.gradle.api.internal.tasks.testing.*
+import org.gradle.api.tasks.testing.TestResult
 import org.gradle.internal.id.LongIdGenerator
-import org.gradle.util.TemporaryFolder
+import org.gradle.logging.StandardOutputRedirector
+import org.gradle.messaging.actor.ActorFactory
+import org.gradle.messaging.actor.TestActorFactory
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.JUnit4GroovyMockery
 import org.jmock.integration.junit4.JMock
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Rule
-import org.junit.Test
+import org.junit.*
 import org.junit.runner.Description
 import org.junit.runner.RunWith
 import org.junit.runner.Runner
 import org.junit.runner.notification.Failure
 import org.junit.runner.notification.RunNotifier
+
 import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-import org.gradle.logging.StandardOutputRedirector
-import org.junit.BeforeClass
-import junit.extensions.TestSetup
-import org.junit.After
-import org.gradle.api.tasks.testing.TestResult
-import junit.framework.TestSuite
-import org.gradle.messaging.actor.ActorFactory
-import org.gradle.messaging.actor.TestActorFactory
+import static org.junit.Assert.assertThat
 
 @RunWith(JMock.class)
 class JUnitTestClassProcessorTest {
     private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder();
+    @Rule public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private final TestResultProcessor resultProcessor = context.mock(TestResultProcessor.class);
     private final ActorFactory actorFactory = new TestActorFactory()
-    private final JUnitTestClassProcessor processor = new JUnitTestClassProcessor(tmpDir.dir, new LongIdGenerator(), actorFactory, {} as StandardOutputRedirector);
+    private final JUnitTestClassProcessor processor = new JUnitTestClassProcessor(tmpDir.testDirectory, new LongIdGenerator(), actorFactory, {} as StandardOutputRedirector);
 
     @Test
     public void executesAJUnit4TestClass() {
@@ -160,6 +152,17 @@ class JUnitTestClassProcessorTest {
                 assertThat(suite.className, equalTo(AnIgnoredTestClass.class.name))
                 assertThat(event.parentId, nullValue())
             }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test, TestStartEvent event ->
+                assertThat(test.id, equalTo(2L))
+                assertThat(test.name, equalTo('ignored'))
+                assertThat(test.className, equalTo(AnIgnoredTestClass.class.name))
+                assertThat(event.parentId, equalTo(1L))
+            }
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, equalTo(TestResult.ResultType.SKIPPED))
+            }
             one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
             will { id, TestCompleteEvent event ->
                 assertThat(event.resultType, nullValue())
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFrameworkTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFrameworkTest.java
index 0753cd4..b4b4a82 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFrameworkTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFrameworkTest.java
@@ -17,13 +17,12 @@
 package org.gradle.api.internal.tasks.testing.junit;
 
 import org.gradle.api.AntBuilder;
-import org.gradle.internal.Factory;
-import org.gradle.internal.id.IdGenerator;
-import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.api.internal.tasks.testing.AbstractTestFrameworkTest;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
-import org.gradle.api.internal.tasks.testing.junit.report.TestReporter;
 import org.gradle.api.tasks.testing.junit.JUnitOptions;
+import org.gradle.internal.Factory;
+import org.gradle.internal.id.IdGenerator;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.messaging.actor.ActorFactory;
 import org.jmock.Expectations;
 import org.junit.Before;
@@ -39,7 +38,6 @@ import static org.junit.Assert.assertThat;
  */
 public class JUnitTestFrameworkTest extends AbstractTestFrameworkTest {
     private JUnitTestFramework jUnitTestFramework;
-    private TestReporter reporterMock;
     private JUnitOptions jUnitOptionsMock;
     private IdGenerator<?> idGenerator;
     private ServiceRegistry serviceRegistry;
@@ -48,7 +46,6 @@ public class JUnitTestFrameworkTest extends AbstractTestFrameworkTest {
     public void setUp() throws Exception {
         super.setUp();
 
-        reporterMock = context.mock(TestReporter.class);
         jUnitOptionsMock = context.mock(JUnitOptions.class);
         idGenerator = context.mock(IdGenerator.class);
         serviceRegistry = context.mock(ServiceRegistry.class);
@@ -57,11 +54,15 @@ public class JUnitTestFrameworkTest extends AbstractTestFrameworkTest {
                 return temporaryDir;
             }
         };
-        context.checking(new Expectations(){{
-            allowing(testMock).getTestClassesDir(); will(returnValue(testClassesDir));
-            allowing(testMock).getClasspath(); will(returnValue(classpathMock));
-            allowing(testMock).getAnt(); will(returnValue(context.mock(AntBuilder.class)));
-            allowing(testMock).getTemporaryDirFactory(); will(returnValue(temporaryDirFactory));
+        context.checking(new Expectations() {{
+            allowing(testMock).getTestClassesDir();
+            will(returnValue(testClassesDir));
+            allowing(testMock).getClasspath();
+            will(returnValue(classpathMock));
+            allowing(testMock).getAnt();
+            will(returnValue(context.mock(AntBuilder.class)));
+            allowing(testMock).getTemporaryDirFactory();
+            will(returnValue(temporaryDirFactory));
         }});
     }
 
@@ -71,7 +72,6 @@ public class JUnitTestFrameworkTest extends AbstractTestFrameworkTest {
         setMocks();
 
         assertNotNull(jUnitTestFramework.getOptions());
-        assertNotNull(jUnitTestFramework.getReporter());
     }
 
     @org.junit.Test
@@ -81,46 +81,19 @@ public class JUnitTestFrameworkTest extends AbstractTestFrameworkTest {
         final ActorFactory actorFactory = context.mock(ActorFactory.class);
 
         context.checking(new Expectations() {{
-            one(testMock).getTestResultsDir(); will(returnValue(testResultsDir));
-            one(serviceRegistry).get(IdGenerator.class); will(returnValue(idGenerator));
-            one(serviceRegistry).get(ActorFactory.class); will(returnValue(actorFactory));
+            one(testMock).getTestResultsDir();
+            will(returnValue(testResultsDir));
+            one(serviceRegistry).get(IdGenerator.class);
+            will(returnValue(idGenerator));
+            one(serviceRegistry).get(ActorFactory.class);
+            will(returnValue(actorFactory));
         }});
 
         TestClassProcessor testClassProcessor = jUnitTestFramework.getProcessorFactory().create(serviceRegistry);
         assertThat(testClassProcessor, instanceOf(JUnitTestClassProcessor.class));
     }
 
-    @org.junit.Test
-    public void testReport() {
-        jUnitTestFramework = new JUnitTestFramework(testMock);
-        setMocks();
-
-        context.checking(new Expectations() {{
-            one(testMock).getTestResultsDir(); will(returnValue(testResultsDir));
-            one(testMock).getTestReportDir(); will(returnValue(testReportDir));
-            one(testMock).isTestReport(); will(returnValue(true));
-            one(reporterMock).setTestReportDir(testReportDir);
-            one(reporterMock).setTestResultsDir(testResultsDir);
-            one(reporterMock).generateReport();
-        }});
-
-        jUnitTestFramework.report();
-    }
-
-    @org.junit.Test
-    public void testReportWithDisabledReport() {
-        jUnitTestFramework = new JUnitTestFramework(testMock);
-        setMocks();
-
-        context.checking(new Expectations() {{
-            one(testMock).isTestReport(); will(returnValue(false));
-        }});
-
-        jUnitTestFramework.report();
-    }
-
     private void setMocks() {
-        jUnitTestFramework.setReporter(reporterMock);
         jUnitTestFramework.setOptions(jUnitOptionsMock);
     }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReportTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReportTest.groovy
index 3db359b..0249361 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReportTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReportTest.groovy
@@ -15,38 +15,34 @@
  */
 package org.gradle.api.internal.tasks.testing.junit.report
 
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
+import org.cyberneko.html.parsers.SAXParser
+import org.gradle.api.Action
+import org.gradle.api.internal.tasks.testing.junit.result.TestClassResult
+import org.gradle.api.internal.tasks.testing.junit.result.TestMethodResult
+import org.gradle.api.internal.tasks.testing.junit.result.TestResultsProvider
+import org.gradle.api.internal.tasks.testing.logging.SimpleTestResult
+import org.gradle.api.tasks.testing.TestOutputEvent
+import org.gradle.api.tasks.testing.TestResult
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.ConfigureUtil
 import org.junit.Rule
 import spock.lang.Specification
-import org.cyberneko.html.parsers.SAXParser
 
 class DefaultTestReportTest extends Specification {
-    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final DefaultTestReport report = new DefaultTestReport()
     final TestFile reportDir = tmpDir.file('report')
-    final TestFile resultsDir = tmpDir.file('results')
     final TestFile indexFile = reportDir.file('index.html')
-
-    def setup() {
-        report.testReportDir = reportDir
-        report.testResultsDir = resultsDir
-    }
-
-    def generatesReportWhenResultsDirectoryDoesNotExist() {
-        when:
-        report.generateReport()
-
-        then:
-        def index = results(indexFile)
-        index.assertHasTests(0)
-    }
+    final TestResultsProvider testResultProvider = Mock()
 
     def generatesReportWhenThereAreNoTestResults() {
-        resultsDir.mkdir()
+        given:
+        emptyResultSet()
 
         when:
-        report.generateReport()
+        report.generateReport(testResultProvider, reportDir)
 
         then:
         def index = results(indexFile)
@@ -57,26 +53,39 @@ class DefaultTestReportTest extends Specification {
         index.assertHasNoNavLinks()
     }
 
+    TestResultsProvider buildResults(Closure closure) {
+        TestResultsBuilder builder = new TestResultsBuilder()
+        ConfigureUtil.configure(closure, builder)
+        return builder;
+    }
+
     def generatesReportWhichIncludesContentsOfEachTestResultFile() {
-        resultsDir.file('TEST-someClass.xml') << '''
-<testsuite name="org.gradle.Test">
-    <testcase classname="org.gradle.Test" name="test1" time="0.0010"/>
-    <testcase classname="org.gradle.Test" name="test2" time="0.0040"/>
-    <system-out>this is
-standard output</system-out>
-    <system-err>this is
-standard error</system-err>
-</testsuite>
-'''
-        resultsDir.file('TEST-someOtherClass.xml') << '''
-<testsuite name="org.gradle.Test2">
-    <testcase classname="org.gradle.Test2" name="test1" time="102.0010"/>
-    <testcase classname="org.gradle.sub.Test" name="test1" time="12.9"/>
-</testsuite>
-'''
+        given:
+        def testTestResults = buildResults {
+            testClassResult("org.gradle.Test") {
+                testcase("test1") {
+                    duration = 1
+                }
+                testcase("test2") {
+                    duration = 4
+                }
+                stdout = "this is\nstandard output"
+                stderr = "this is\nstandard error"
+            }
+            testClassResult("org.gradle.Test2") {
+                testcase("test3") {
+                    duration = 102001
+                }
+            }
+            testClassResult("org.gradle.sub.Test") {
+                testcase("test4") {
+                    duration = 12900
+                }
+            }
+        }
 
         when:
-        report.generateReport()
+        report.generateReport(testTestResults, reportDir)
 
         then:
         def index = results(indexFile)
@@ -110,20 +119,34 @@ standard error</system-err>
     }
 
     def generatesReportWhenThereAreFailures() {
-        resultsDir.file('TEST-someClass.xml') << '''
-<testsuite>
-    <testcase classname="org.gradle.Test" name="test1" time="0"><failure message="something failed">this is the failure
-at someClass
-</failure></testcase>
-    <testcase classname="org.gradle.Test" name="test2" time="0"><failure message="a multi-line
-message">this is a failure.</failure></testcase>
-    <testcase classname="org.gradle.Test2" name="test1" time="0"/>
-    <testcase classname="org.gradle.sub.Test" name="test1" time="0"/>
-</testsuite>
-'''
-
+        given:
+        def testTestResults = buildResults {
+            testClassResult("org.gradle.Test") {
+                testcase("test1") {
+                    duration = 0
+                    failure("something failed", "this is the failure\nat someClass")
+                }
+                testcase("test2") {
+                    duration = 0
+                    failure("a multi-line\nmessage\"", "this is a failure.")
+                }
+                stdout = "this is\nstandard output"
+                stderr = "this is\nstandard error"
+            }
+
+            testClassResult("org.gradle.Test2") {
+                testcase("test1") {
+                    duration = 0
+                }
+            }
+            testClassResult("org.gradle.sub.Test") {
+                testcase("test1") {
+                    duration = 0
+                }
+            }
+        }
         when:
-        report.generateReport()
+        report.generateReport(testTestResults, reportDir)
 
         then:
         def index = results(indexFile)
@@ -149,14 +172,16 @@ message">this is a failure.</failure></testcase>
     }
 
     def generatesReportWhenThereAreIgnoredTests() {
-        resultsDir.file('TEST-someClass.xml') << '''
-<testsuite>
-    <ignored-testcase classname="org.gradle.Test" name="test1"/>
-</testsuite>
-'''
-
+        given:
+        def testTestResults = buildResults {
+            testClassResult("org.gradle.Test") {
+                testcase("test1") {
+                    resultType = TestResult.ResultType.SKIPPED
+                }
+            }
+        }
         when:
-        report.generateReport()
+        report.generateReport(testTestResults, reportDir)
 
         then:
         def index = results(indexFile)
@@ -178,15 +203,16 @@ message">this is a failure.</failure></testcase>
     }
 
     def reportsOnClassesInDefaultPackage() {
-        resultsDir.file('TEST-someClass.xml') << '''
-<testsuite name="Test">
-    <testcase classname="Test" name="test1" time="0">
-    </testcase>
-</testsuite>
-'''
-
+        given:
+        def testTestResults = buildResults {
+            testClassResult("Test") {
+                testcase("test1") {
+                    duration = 0
+                }
+            }
+        }
         when:
-        report.generateReport()
+        report.generateReport(testTestResults, reportDir)
 
         then:
         def index = results(indexFile)
@@ -198,18 +224,19 @@ message">this is a failure.</failure></testcase>
     }
 
     def escapesHtmlContentInReport() {
-        resultsDir.file('TEST-someClass.xml') << '''
-<testsuite name="org.gradle.Test">
-    <testcase classname="org.gradle.Test" name="test1 < test2" time="0">
-        <failure message="something failed"><a failure></failure>
-    </testcase>
-    <system-out></html> & </system-out>
-    <system-err></div> & </system-err>
-</testsuite>
-'''
-
+        given:
+        def testTestResults = buildResults {
+            testClassResult("org.gradle.Test") {
+                testcase("test1 < test2") {
+                    duration = 0
+                    failure("something failed", "<a failure>")
+                }
+                stdout = "</html> & "
+                stderr = "</div> & "
+            }
+        }
         when:
-        report.generateReport()
+        report.generateReport(testTestResults, reportDir)
 
         then:
         def testClassFile = results(reportDir.file('org.gradle.Test.html'))
@@ -220,16 +247,18 @@ message">this is a failure.</failure></testcase>
     }
 
     def encodesUnicodeCharactersInReport() {
-        resultsDir.file('TEST-someClass.xml') << '''
-<testsuite name="org.gradle.Test">
-    <testcase classname="org.gradle.Test" name="&#x0107;" time="0"/>
-    <system-out>out:&#x0256;</system-out>
-    <system-err>err:&#x0102;</system-err>
-</testsuite>
-'''
-
+        given:
+        def testTestResults = buildResults {
+            testClassResult("org.gradle.Test") {
+                testcase('\u0107') {
+                    duration = 0
+                }
+                stdout = "out:\u0256"
+                stderr = "err:\u0102"
+            }
+        }
         when:
-        report.generateReport()
+        report.generateReport(testTestResults, reportDir)
 
         then:
         def testClassFile = results(reportDir.file('org.gradle.Test.html'))
@@ -238,23 +267,93 @@ message">this is a failure.</failure></testcase>
         testClassFile.assertHasStandardError('err:\u0102')
     }
 
-    def ignoresFilesWhichAreNotResultFiles() {
-        resultsDir.file('TEST-someClass.xml') << '''
-<testsuite name="org.gradle.Test">
-    <testcase classname="org.gradle.Test" name="test1" time="0"></testcase>
-</testsuite>
-'''
-        resultsDir.file('TESTS-broken.xml') << 'broken'
+    def results(TestFile file) {
+        return new TestResultsFixture(file)
+    }
 
-        when:
-        report.generateReport()
+    def emptyResultSet() {
+        _ * testResultProvider.visitClasses(_)
+    }
+}
 
-        then:
-        results(indexFile).assertHasTests(1)
+class TestResultsBuilder implements TestResultsProvider {
+    def testClasses = [:]
+
+    void testClassResult(String className, Closure configClosure) {
+        BuildableTestClassResult testSuite = new BuildableTestClassResult(className, System.currentTimeMillis())
+        ConfigureUtil.configure(configClosure, testSuite)
+
+        testClasses[className] = testSuite
     }
 
-    def results(TestFile file) {
-        return new TestResultsFixture(file)
+    void writeOutputs(String className, TestOutputEvent.Destination destination, Writer writer) {
+        if (destination == TestOutputEvent.Destination.StdOut) {
+            writer.append(testClasses[className].stdout);
+        } else if (destination == TestOutputEvent.Destination.StdErr) {
+            writer.append(testClasses[className].stderr);
+        }
+    }
+
+    void visitClasses(Action<? super TestClassResult> visitor) {
+        testClasses.values().each {
+            visitor.execute(it)
+        }
+    }
+
+    private static class BuildableTestClassResult extends TestClassResult {
+        String stderr;
+        String stdout;
+
+        BuildableTestClassResult(String className, long startTime) {
+            super(className, startTime)
+        }
+
+        TestMethodResult testcase(String name) {
+            BuildableTestMethodResult methodResult = new BuildableTestMethodResult(name, new SimpleTestResult())
+            add(methodResult)
+            return methodResult
+        }
+
+        def testcase(String name, Closure configClosure) {
+            BuildableTestMethodResult methodResult = testcase(name);
+            ConfigureUtil.configure(configClosure, methodResult)
+            return methodResult
+        }
+    }
+
+    private static class BuildableTestMethodResult extends TestMethodResult {
+        long duration
+        List<Throwable> exceptions = []
+        TestResult.ResultType resultType = TestResult.ResultType.SUCCESS
+
+        BuildableTestMethodResult(String name, TestResult result) {
+            super(name, result)
+            duration = result.endTime - result.startTime;
+        }
+
+        void failure(String message, String text) {
+            exceptions.add(new ResultException(message, text));
+        }
+    }
+
+    private static class ResultException extends Exception {
+        private final String message
+        private final String text
+
+        public ResultException(String message, String text) {
+            this.text = text
+            this.message = message
+        }
+
+        public String toString() {
+            return message
+        }
+
+        public void printStackTrace(PrintWriter s) {
+            s.print(text);
+        }
+
+
     }
 }
 
@@ -301,7 +400,7 @@ class TestResultsFixture {
         assert counter != null
         assert counter.text() == '-'
     }
-    
+
     void assertHasSuccessRate(int rate) {
         Node testDiv = content.depthFirst().find { it.'@id' == 'successRate' }
         assert testDiv != null
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/Binary2JUnitXmlReportGeneratorSpec.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/Binary2JUnitXmlReportGeneratorSpec.groovy
new file mode 100644
index 0000000..1715eb5
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/Binary2JUnitXmlReportGeneratorSpec.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit.result
+
+import org.gradle.api.tasks.testing.TestResult
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.api.GradleException
+import org.gradle.api.Action
+
+/**
+ * by Szczepan Faber, created at: 11/19/12
+ */
+class Binary2JUnitXmlReportGeneratorSpec extends Specification {
+
+    @Rule private TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+    private resultsProvider = Mock(TestResultsProvider)
+    private generator = new Binary2JUnitXmlReportGenerator(temp.testDirectory, resultsProvider)
+
+    def setup() {
+        generator.saxWriter = Mock(JUnitXmlResultWriter)
+    }
+
+    def "writes results"() {
+        def fooTest = new TestClassResult('FooTest', 100)
+            .add(new TestMethodResult("foo", Mock(TestResult)))
+
+        def barTest = new TestClassResult('BarTest', 100)
+            .add(new TestMethodResult("bar", Mock(TestResult)))
+            .add(new TestMethodResult("bar2", Mock(TestResult)))
+
+        resultsProvider.visitClasses(_) >> { Action action ->
+            action.execute(fooTest)
+            action.execute(barTest)
+        }
+
+        when:
+        generator.generate()
+
+        then:
+        1 * generator.saxWriter.write(fooTest, _)
+        1 * generator.saxWriter.write(barTest, _)
+        0 * generator.saxWriter._
+    }
+
+    def "adds context information to the failure if something goes wrong"() {
+        def fooTest = new TestClassResult('FooTest', 100)
+                .add(new TestMethodResult("foo", Mock(TestResult)))
+
+        resultsProvider.visitClasses(_) >> { Action action ->
+            action.execute(fooTest)
+        }
+        generator.saxWriter.write(fooTest, _) >> { throw new IOException("Boo!") }
+
+        when:
+        generator.generate()
+
+        then:
+        def ex = thrown(GradleException)
+        ex.message.startsWith('Could not write XML test results for FooTest')
+        ex.cause.message == "Boo!"
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/CachingFileWriterSpec.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/CachingFileWriterSpec.groovy
new file mode 100644
index 0000000..aaf046e
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/CachingFileWriterSpec.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit.result
+
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 11/19/12
+ */
+class CachingFileWriterSpec extends Specification {
+
+    private @Rule TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+    private writer = new CachingFileWriter(2)
+
+    def cleanup() {
+        writer.closeAll()
+    }
+
+    def "keeps n files open"() {
+        when:
+        writer.write(temp.file("1.txt"), "1")
+
+        then:
+        writer.openFiles.keySet()*.name == ["1.txt"]
+
+        when:
+        writer.write(temp.file("2.txt"), "2")
+
+        then:
+        writer.openFiles.keySet()*.name == ["1.txt", "2.txt"]
+
+        when:
+        writer.write(temp.file("3.txt"), "3")
+
+        then:
+        writer.openFiles.keySet()*.name == ["2.txt", "3.txt"]
+
+        when:
+        writer.closeAll()
+
+        then:
+        writer.openFiles.isEmpty()
+    }
+
+    def "writes to files"() {
+        when:
+        writer.write(temp.file("1.txt"), "1")
+        writer.write(temp.file("2.txt"), "2")
+        writer.write(temp.file("3.txt"), "3")
+        writer.write(temp.file("4.txt"), "4")
+        writer.write(temp.file("1.txt"), "a")
+        writer.write(temp.file("2.txt"), "b")
+        writer.write(temp.file("3.txt"), "c")
+
+        and:
+        writer.closeAll()
+
+        then:
+        writer.openFiles.isEmpty()
+
+        and:
+        temp.file("1.txt").text == '1a'
+        temp.file("2.txt").text == '2b'
+        temp.file("3.txt").text == '3c'
+        temp.file("4.txt").text == '4'
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/JUnitXmlResultWriterSpec.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/JUnitXmlResultWriterSpec.groovy
new file mode 100644
index 0000000..112b659
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/JUnitXmlResultWriterSpec.groovy
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit.result
+
+import org.gradle.api.internal.tasks.testing.results.DefaultTestResult
+import org.gradle.integtests.fixtures.JUnitTestClassExecutionResult
+import spock.lang.Specification
+
+import static java.util.Arrays.asList
+import static java.util.Collections.emptyList
+import static org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdErr
+import static org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdOut
+import static org.gradle.api.tasks.testing.TestResult.ResultType.*
+import static org.hamcrest.Matchers.equalTo
+import org.gradle.internal.SystemProperties
+
+/**
+ * by Szczepan Faber, created at: 11/16/12
+ */
+class JUnitXmlResultWriterSpec extends Specification {
+
+    private provider = Mock(TestResultsProvider)
+    private generator = new JUnitXmlResultWriter("localhost", provider)
+
+    private startTime = 1353344968049
+
+    def "writes xml JUnit result"() {
+        TestClassResult result = new TestClassResult("com.foo.FooTest", startTime)
+        result.add(new TestMethodResult("some test", new DefaultTestResult(SUCCESS, startTime + 10, startTime + 25, 1, 1, 0, emptyList())))
+        result.add(new TestMethodResult("some test two", new DefaultTestResult(SUCCESS, startTime + 15, startTime + 30, 1, 1, 0, emptyList())))
+        result.add(new TestMethodResult("some failing test", new DefaultTestResult(FAILURE, startTime + 30, startTime + 40, 1, 0, 1, [new RuntimeException("Boo!")])))
+        result.add(new TestMethodResult("some skipped test", new DefaultTestResult(SKIPPED, startTime + 35, startTime + 45, 1, 0, 1, asList())))
+
+        provider.writeOutputs("com.foo.FooTest", StdOut, _) >> { args -> args[2].write("1st output message\n2nd output message\n") }
+        provider.writeOutputs("com.foo.FooTest", StdErr, _) >> { args -> args[2].write("err") }
+
+        when:
+        def xml = getXml(result)
+
+        then:
+        new JUnitTestClassExecutionResult(xml, "com.foo.FooTest")
+            .assertTestCount(4, 1, 0)
+            .assertTestFailed("some failing test", equalTo('java.lang.RuntimeException: Boo!'))
+            .assertTestsSkipped("some skipped test")
+            .assertTestsExecuted("some test", "some test two", "some failing test")
+            .assertStdout(equalTo("""1st output message
+2nd output message
+"""))
+            .assertStderr(equalTo("err"))
+
+        and:
+        xml.startsWith """<?xml version="1.1" encoding="UTF-8"?>
+<testsuite name="com.foo.FooTest" tests="4" failures="1" errors="0" timestamp="2012-11-19T17:09:28" hostname="localhost" time="0.045">
+  <properties/>
+  <testcase name="some test" classname="com.foo.FooTest" time="0.015"/>
+  <testcase name="some test two" classname="com.foo.FooTest" time="0.015"/>
+  <testcase name="some failing test" classname="com.foo.FooTest" time="0.01">
+    <failure message="java.lang.RuntimeException: Boo!" type="java.lang.RuntimeException">java.lang.RuntimeException: Boo!"""
+
+        xml.endsWith """</failure>
+  </testcase>
+  <ignored-testcase name="some skipped test" classname="com.foo.FooTest" time="0.01"/>
+  <system-out><![CDATA[1st output message
+2nd output message
+]]></system-out>
+  <system-err><![CDATA[err]]></system-err>
+</testsuite>
+"""
+    }
+
+    def "writes results with empty outputs"() {
+        TestClassResult result = new TestClassResult("com.foo.FooTest", startTime)
+        result.add(new TestMethodResult("some test", new DefaultTestResult(SUCCESS, startTime + 100, startTime + 300, 1, 1, 0, emptyList())))
+        _ * provider.writeOutputs(_, _, _)
+
+        when:
+        def xml = getXml(result)
+
+        then:
+        xml == """<?xml version="1.1" encoding="UTF-8"?>
+<testsuite name="com.foo.FooTest" tests="1" failures="0" errors="0" timestamp="2012-11-19T17:09:28" hostname="localhost" time="0.3">
+  <properties/>
+  <testcase name="some test" classname="com.foo.FooTest" time="0.2"/>
+  <system-out><![CDATA[]]></system-out>
+  <system-err><![CDATA[]]></system-err>
+</testsuite>
+"""
+    }
+
+    def "encodes xml"() {
+        TestClassResult result = new TestClassResult("com.foo.FooTest", startTime)
+        result.add(new TestMethodResult("some test", new DefaultTestResult(FAILURE, 100, 300, 1, 1, 0, [new RuntimeException("<> encoded!")])))
+        provider.writeOutputs(_, StdErr, _) >> { args -> args[2].write("with CDATA end token: ]]> some ascii: ż") }
+        provider.writeOutputs(_, StdOut, _) >> { args -> args[2].write("with CDATA end token: ]]> some ascii: ż") }
+
+        when:
+        def xml = getXml(result)
+
+        then:
+        //attribute and text is encoded:
+        xml.contains('message="java.lang.RuntimeException: <> encoded!" type="java.lang.RuntimeException">java.lang.RuntimeException: <> encoded!')
+        //output encoded:
+        xml.contains('<system-out><![CDATA[with CDATA end token: ]]]]><![CDATA[> some ascii: ż]]></system-out>')
+        xml.contains('<system-err><![CDATA[with CDATA end token: ]]]]><![CDATA[> some ascii: ż]]></system-err>')
+    }
+
+    def "writes results with no tests"() {
+        TestClassResult result = new TestClassResult("com.foo.IgnoredTest", startTime)
+
+        when:
+        def xml = getXml(result)
+
+        then:
+        xml == """<?xml version="1.1" encoding="UTF-8"?>
+<testsuite name="com.foo.IgnoredTest" tests="0" failures="0" errors="0" timestamp="2012-11-19T17:09:28" hostname="localhost" time="0.0">
+  <properties/>
+  <system-out><![CDATA[]]></system-out>
+  <system-err><![CDATA[]]></system-err>
+</testsuite>
+"""
+    }
+
+    def getXml(TestClassResult result) {
+        def text = new ByteArrayOutputStream()
+        generator.write(result, text)
+        return text.toString("UTF-8").replace(SystemProperties.lineSeparator, "\n")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestClassResultSpec.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestClassResultSpec.groovy
new file mode 100644
index 0000000..cd3bbe2
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestClassResultSpec.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit.result
+
+import org.gradle.api.internal.tasks.testing.results.DefaultTestResult
+import org.gradle.api.tasks.testing.TestResult
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 11/19/12
+ */
+class TestClassResultSpec extends Specification {
+
+    def "provides test class result information"() {
+        def result = new TestClassResult('class', 100)
+        assert result.duration == 0
+
+        when:
+        result.add(new TestMethodResult("foo",   new DefaultTestResult(TestResult.ResultType.SUCCESS, 100, 200, 1, 1, 0, [])))
+        result.add(new TestMethodResult("fail",  new DefaultTestResult(TestResult.ResultType.FAILURE, 250, 300, 1, 0, 1, [new RuntimeException("bar")])))
+        result.add(new TestMethodResult("fail2", new DefaultTestResult(TestResult.ResultType.FAILURE, 300, 450, 1, 0, 1, [new RuntimeException("foo")])))
+
+        then:
+        result.failuresCount == 2
+        result.testsCount == 3
+        result.duration == 350
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollectorSpec.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollectorSpec.groovy
new file mode 100644
index 0000000..858226c
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollectorSpec.groovy
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit.result
+
+import org.gradle.api.internal.tasks.testing.*
+import org.gradle.api.internal.tasks.testing.results.DefaultTestResult
+import org.gradle.api.tasks.testing.TestOutputEvent
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+import static java.util.Arrays.asList
+import static org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdErr
+import static org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdOut
+import static org.gradle.api.tasks.testing.TestResult.ResultType.FAILURE
+import static org.gradle.api.tasks.testing.TestResult.ResultType.SUCCESS
+import org.gradle.api.Action
+
+/**
+ * by Szczepan Faber, created at: 11/19/12
+ */
+class TestReportDataCollectorSpec extends Specification {
+
+    @Rule
+    private TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+    private CachingFileWriter fileWriter = Mock()
+    private TestResultSerializer serializer = Mock()
+    private collector = new TestReportDataCollector(temp.testDirectory, fileWriter, serializer)
+
+    def "closes all files when root finishes"() {
+        def root = new DefaultTestSuiteDescriptor("1", "Suite")
+        def clazz = new DecoratingTestDescriptor(new DefaultTestClassDescriptor("1.1", "Class"), root)
+
+        def dummyResult = new DefaultTestResult(SUCCESS, 0, 0, 0, 0, 0, asList())
+
+        when:
+        collector.afterSuite(clazz, dummyResult)
+
+        then:
+        0 * fileWriter.closeAll()
+
+        when:
+        collector.afterSuite(root, dummyResult)
+
+        then:
+        1 * fileWriter.closeAll()
+    }
+
+    def "writes results when root finishes"() {
+        def root = new DefaultTestSuiteDescriptor("1", "Suite")
+        def clazz = new DecoratingTestDescriptor(new DefaultTestClassDescriptor("1.1", "Class"), root)
+
+        def dummyResult = new DefaultTestResult(SUCCESS, 0, 0, 0, 0, 0, asList())
+
+        when:
+        collector.afterSuite(clazz, dummyResult)
+
+        then:
+        0 * serializer._
+
+        when:
+        collector.afterSuite(root, dummyResult)
+
+        then:
+        1 * serializer.write(_, temp.testDirectory)
+        0 * serializer._
+    }
+
+    def "keeps track of test results"() {
+        def root = new DefaultTestSuiteDescriptor("1", "Suite")
+        def clazz = new DecoratingTestDescriptor(new DefaultTestClassDescriptor("1.1", "FooTest"), root)
+        def test1 = new DecoratingTestDescriptor(new DefaultTestDescriptor("1.1.1", "FooTest", "testMethod"), clazz)
+        def result1 = new DefaultTestResult(SUCCESS, 100, 200, 1, 1, 0, asList())
+
+        def test2 = new DecoratingTestDescriptor(new DefaultTestDescriptor("1.1.2", "FooTest", "testMethod2"), clazz)
+        def result2 = new DefaultTestResult(FAILURE, 250, 300, 1, 0, 1, asList(new RuntimeException("Boo!")))
+
+        when:
+        //simulating TestNG, where we don't receive beforeSuite for classes
+        collector.beforeSuite(root)
+
+        collector.beforeTest(test1)
+        collector.beforeTest(test2)
+
+        collector.afterTest(test1, result1)
+        collector.afterTest(test2, result2)
+
+        collector.afterSuite(root, new DefaultTestResult(FAILURE, 0, 500, 2, 1, 1, asList(new RuntimeException("Boo!"))))
+
+        def results = []
+        collector.visitClasses({ results << it } as Action)
+
+        then:
+        results.size() == 1
+        def fooTest = results[0]
+        fooTest.className == 'FooTest'
+        fooTest.startTime == 100
+        fooTest.testsCount == 2
+        fooTest.failuresCount == 1
+        fooTest.duration == 200
+        fooTest.results.size() == 2
+        fooTest.results.find { it.name == 'testMethod' && it.endTime == 200 && it.duration == 100 }
+        fooTest.results.find { it.name == 'testMethod2' && it.endTime == 300 && it.duration == 50 }
+    }
+
+    def "keeps track of outputs"() {
+        def test = new DefaultTestDescriptor("1.1.1", "FooTest", "testMethod")
+        def test2 = new DefaultTestDescriptor("1.1.2", "FooTest", "testMethod2")
+        def suite = new DefaultTestSuiteDescriptor("1", "Suite")
+
+        when:
+        collector.onOutput(suite, new DefaultTestOutputEvent(StdOut, "out"))
+        collector.onOutput(test, new DefaultTestOutputEvent(StdErr, "err"))
+        collector.onOutput(test2, new DefaultTestOutputEvent(StdOut, "out"))
+
+        then:
+        1 * fileWriter.write(new File(temp.testDirectory, "FooTest.stderr"), "err")
+        1 * fileWriter.write(new File(temp.testDirectory, "FooTest.stdout"), "out")
+        0 * fileWriter._
+    }
+
+    def "provides outputs"() {
+        def collector = new TestReportDataCollector(temp.testDirectory)
+        def test = new DefaultTestDescriptor("1.1.1", "FooTest", "testMethod")
+        def test2 = new DefaultTestDescriptor("1.1.2", "FooTest", "testMethod2")
+        def test3 = new DefaultTestDescriptor("1.1.3", "BarTest", "testMethod")
+
+        when:
+        collector.onOutput(test, new DefaultTestOutputEvent(StdErr, "err"))
+        collector.onOutput(test, new DefaultTestOutputEvent(StdErr, "err2"))
+        collector.onOutput(test2, new DefaultTestOutputEvent(StdOut, "out"))
+        collector.onOutput(test3, new DefaultTestOutputEvent(StdOut, "out, don't show"))
+
+        collector.afterSuite(new DefaultTestSuiteDescriptor("1", "suite"), null) //force closing of files
+
+        then:
+        collectOutput("FooTest", StdErr) == 'errerr2'
+        collectOutput("FooTest", StdOut) == 'out'
+
+        collectOutput("TestWithoutOutputs", StdErr) == ''
+        collectOutput("TestWithoutOutputs", StdOut) == ''
+    }
+
+    String collectOutput(String className, TestOutputEvent.Destination destination) {
+        def writer = new StringWriter()
+        collector.writeOutputs(className, destination, writer)
+        return writer.toString()
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultSerializerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultSerializerTest.groovy
new file mode 100644
index 0000000..d554f26
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultSerializerTest.groovy
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks.testing.junit.result
+
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.api.tasks.testing.TestResult
+import org.gradle.messaging.remote.internal.PlaceholderException
+import org.gradle.api.Action
+
+class TestResultSerializerTest extends Specification {
+    @Rule
+    private TestNameTestDirectoryProvider tmp = new TestNameTestDirectoryProvider()
+    final TestResultSerializer serializer = new TestResultSerializer()
+
+    def "can write and read results"() {
+        def class1 = new TestClassResult('Class1', 1234)
+        def failure = new RuntimeException("broken")
+        def method1 = new TestMethodResult("method1", TestResult.ResultType.SUCCESS, 100, 2300, [])
+        def method2 = new TestMethodResult("method2", TestResult.ResultType.FAILURE, 200, 2700, [failure])
+        class1.add(method1)
+        class1.add(method2)
+        def class2 = new TestClassResult('Class2', 5678)
+        def results = [class1, class2]
+
+        when:
+        def read = serialize(results)
+
+        then:
+        read.size() == 2
+        def readClass1 = read[0]
+        readClass1.className == 'Class1'
+        readClass1.startTime == 1234
+        readClass1.results.size() == 2
+
+        def readMethod1 = readClass1.results[0]
+        readMethod1.name == 'method1'
+        readMethod1.resultType == TestResult.ResultType.SUCCESS
+        readMethod1.duration == 100
+        readMethod1.endTime == 2300
+        readMethod1.exceptions.empty
+
+        def readMethod2 = readClass1.results[1]
+        readMethod2.name == 'method2'
+        readMethod2.resultType == TestResult.ResultType.FAILURE
+        readMethod2.duration == 200
+        readMethod2.endTime == 2700
+        readMethod2.exceptions.size() == 1
+        readMethod2.exceptions[0].class == failure.class
+        readMethod2.exceptions[0].message == failure.message
+        readMethod2.exceptions[0].stackTrace == failure.stackTrace
+
+        def readClass2 = read[1]
+        readClass2.className == 'Class2'
+        readClass2.startTime == 5678
+        readClass2.results.empty
+    }
+
+    def "can write and read exceptions that are not serializable"() {
+        def class1 = new TestClassResult('Class1', 1234)
+        def failure = new RuntimeException("broken") {
+            final Object field = new Object()
+        }
+        def method1 = new TestMethodResult("method1", TestResult.ResultType.FAILURE, 200, 2700, [failure])
+        class1.add(method1)
+        def results = [class1]
+
+        when:
+        def read = serialize(results)
+
+        then:
+        read.size() == 1
+        def readClass1 = read[0]
+        def readMethod1 = readClass1.results[0]
+        readMethod1.exceptions.size() == 1
+        readMethod1.exceptions[0].class == PlaceholderException.class
+        readMethod1.exceptions[0].message == failure.message
+        readMethod1.exceptions[0].toString() == failure.toString()
+        readMethod1.exceptions[0].stackTrace == failure.stackTrace
+    }
+
+    List<TestClassResult> serialize(Collection<TestClassResult> results) {
+        def dir = tmp.createDir("results")
+        serializer.write(results, dir)
+        def result = []
+        serializer.read(dir, { result << it } as Action)
+        return result
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/FullExceptionFormatterTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/FullExceptionFormatterTest.groovy
index 86ef784..86a2bd3 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/FullExceptionFormatterTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/FullExceptionFormatterTest.groovy
@@ -204,10 +204,10 @@ class FullExceptionFormatterTest extends Specification {
         testLogging.getShowStackTraces() >> true
         testLogging.getStackTraceFilters() >> EnumSet.of(TestStackTraceFilter.ENTRY_POINT)
 
-        def cause = new PlaceholderException(RuntimeException.name, "oops", null)
+        def cause = new PlaceholderException(RuntimeException.name, "oops", "java.lang.RuntimeException: oops", null, null)
         cause.stackTrace = createGroovyTrace()
 
-        def exception = new PlaceholderException(Exception.name, "ouch", cause)
+        def exception = new PlaceholderException(Exception.name, "ouch", "java.lang.Exception: ouch", null, cause)
         exception.stackTrace = createGroovyTrace()[1..-1]
 
         expect:
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/ShortExceptionFormatterTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/ShortExceptionFormatterTest.groovy
index c58c125..83195fa 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/ShortExceptionFormatterTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/ShortExceptionFormatterTest.groovy
@@ -59,7 +59,7 @@ class ShortExceptionFormatterTest extends Specification {
     }
 
     def "formats PlaceholderException's correctly"() {
-        def exception = new PlaceholderException(Exception.class.name, "oops", null)
+        def exception = new PlaceholderException(Exception.class.name, "oops", "java.lang.Exception: oops", null, null)
         testDescriptor.className = getClass().name
 
         expect:
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy
index 0af2f40..cc6745d 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy
@@ -17,286 +17,175 @@
 package org.gradle.api.internal.tasks.testing.testng
 
 import org.gradle.api.GradleException
+import org.gradle.api.internal.tasks.testing.TestClassRunInfo
+import org.gradle.api.internal.tasks.testing.TestResultProcessor
+import org.gradle.api.internal.tasks.testing.TestStartEvent
 import org.gradle.api.tasks.testing.TestResult.ResultType
 import org.gradle.api.tasks.testing.testng.TestNGOptions
 import org.gradle.internal.id.LongIdGenerator
 import org.gradle.logging.StandardOutputRedirector
-import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.TemporaryFolder
-import org.jmock.Sequence
-import org.jmock.integration.junit4.JMock
-import org.junit.Ignore
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.gradle.api.internal.tasks.testing.*
 import org.testng.annotations.*
+import spock.lang.Ignore
+import spock.lang.Specification
 
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.assertThat
-import static org.junit.Assert.fail
-
- at RunWith(JMock.class)
-class TestNGTestClassProcessorTest {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    @Rule public final TemporaryFolder reportDir = new TemporaryFolder();
-    @Rule public final TemporaryFolder resultsDir = new TemporaryFolder();
-    private final TestResultProcessor resultProcessor = context.mock(TestResultProcessor.class);
-    private final TestNGOptions options = new TestNGOptions(reportDir.dir)
-    private final TestNGTestClassProcessor processor = new TestNGTestClassProcessor(reportDir.dir, options, [], new LongIdGenerator(), {} as StandardOutputRedirector, resultsDir.dir, true);
-
-    @Test
-    public void executesATestClass() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite, TestStartEvent event ->
-                assertThat(suite.id, equalTo(1L))
-                assertThat(suite.name, equalTo('Gradle test'))
-                assertThat(suite.className, nullValue())
-                assertThat(event.parentId, nullValue())
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test, TestStartEvent event ->
-                assertThat(test.id, equalTo(2L))
-                assertThat(test.name, equalTo('ok'))
-                assertThat(test.className, equalTo(ATestNGClass.class.name))
-                assertThat(event.parentId, equalTo(1L))
-            }
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, equalTo(ResultType.SUCCESS))
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
+class TestNGTestClassProcessorTest extends Specification {
 
+    @Rule TestNameTestDirectoryProvider reportDir = new TestNameTestDirectoryProvider()
+
+    private resultProcessor = Mock(TestResultProcessor)
+
+    private TestNGOptions options = new TestNGOptions(reportDir.testDirectory)
+    private TestNGTestClassProcessor processor  = new TestNGTestClassProcessor(reportDir.testDirectory, options, [], new LongIdGenerator(), {} as StandardOutputRedirector, true);
+
+    void "executes the test class"() {
+        when:
         processor.startProcessing(resultProcessor);
         processor.processTestClass(testClass(ATestNGClass.class));
         processor.stop();
-    }
 
-    @Test
-    public void executesAFactoryTestClass() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite ->
-                assertThat(suite.name, equalTo('Gradle test'))
-                assertThat(suite.className, nullValue())
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.name, equalTo('ok'))
-                assertThat(test.className, equalTo(ATestNGClass.class.name))
-            }
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, equalTo(ResultType.SUCCESS))
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
+        then:
+        1 * resultProcessor.started({ it.id == 1 && it.name == 'Gradle test' && it.className == null }, { it.parentId == null })
+        then:
+        1 * resultProcessor.started({ it.id == 2 && it.name == 'ok' && it.className == ATestNGClass.class.name }, { it.parentId == 1 })
+
+        then:
+        1 * resultProcessor.completed(2, { it.resultType == ResultType.SUCCESS })
+        then:
+        1 * resultProcessor.completed(1, { it.resultType == null })
 
+        0 * resultProcessor._
+    }
+
+    void "executes factory test class"() {
+        when:
         processor.startProcessing(resultProcessor);
         processor.processTestClass(testClass(ATestNGFactoryClass.class));
         processor.stop();
-    }
 
-    @Test
-    public void executesATestWithExpectedException() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.id, equalTo(2L))
-                assertThat(test.name, equalTo('ok'))
-                assertThat(test.className, equalTo(ATestNGClassWithExpectedException.class.name))
-            }
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, equalTo(ResultType.SUCCESS))
-            }
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-        }
+        then:
+        1 * resultProcessor.started({ it.name == 'Gradle test' && it.className == null }, { it.parentId == null })
+        1 * resultProcessor.started({ it.name == 'ok' && it.className == ATestNGClass.class.name }, _ as TestStartEvent)
+
+        1 * resultProcessor.completed(2, { it.resultType == ResultType.SUCCESS })
+        1 * resultProcessor.completed(1, { it.resultType == null })
 
+        0 * resultProcessor._
+    }
+
+    void "executes test with expected exception"() {
+        when:
         processor.startProcessing(resultProcessor);
         processor.processTestClass(testClass(ATestNGClassWithExpectedException.class));
         processor.stop();
-    }
 
-    @Test
-    public void executesATestClassWithBrokenSetup() {
-        context.checking {
-            Sequence sequence = context.sequence('seq')
-
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite ->
-                assertThat(suite.name, equalTo('Gradle test'))
-                assertThat(suite.className, nullValue())
-            }
-            inSequence(sequence)
-
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.name, equalTo('beforeMethod'))
-                assertThat(test.className, equalTo(ATestNGClassWithBrokenSetupMethod.class.name))
-            }
-            inSequence(sequence)
-
-            one(resultProcessor).failure(2L, ATestNGClassWithBrokenSetupMethod.failure)
-
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, equalTo(ResultType.FAILURE))
-            }
-            inSequence(sequence)
-
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.name, equalTo('test'))
-                assertThat(test.className, equalTo(ATestNGClassWithBrokenSetupMethod.class.name))
-            }
-            inSequence(sequence)
-
-            one(resultProcessor).completed(withParam(equalTo(3L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, equalTo(ResultType.SKIPPED))
-            }
-            inSequence(sequence)
-
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            inSequence(sequence)
-        }
+        then:
+        1 * resultProcessor.started({ it.id == 1} , _)
+        1 * resultProcessor.started({ it.name == 'ok' && it.className == ATestNGClassWithExpectedException.class.name }, _)
+
+        1 * resultProcessor.completed(2, { it.resultType == ResultType.SUCCESS })
+        1 * resultProcessor.completed(1, { it.resultType == null })
 
+        0 * resultProcessor._
+    }
+
+    void "executes test with broken setup"() {
+        when:
         processor.startProcessing(resultProcessor);
         processor.processTestClass(testClass(ATestNGClassWithBrokenSetupMethod.class));
         processor.stop();
-    }
 
-    @Test
-    public void executesATestClassWithDependencyMethod() {
-        context.checking {
-            Sequence sequence = context.sequence('seq')
-
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite ->
-                assertThat(suite.name, equalTo('Gradle test'))
-                assertThat(suite.className, nullValue())
-            }
-            inSequence(sequence)
-
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.name, equalTo('beforeMethod'))
-                assertThat(test.className, equalTo(ATestNGClassWithBrokenDependencyMethod.class.name))
-            }
-            inSequence(sequence)
-
-            one(resultProcessor).failure(2L, ATestNGClassWithBrokenDependencyMethod.failure)
-
-            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, equalTo(ResultType.FAILURE))
-            }
-            inSequence(sequence)
-
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.name, equalTo('test'))
-                assertThat(test.className, equalTo(ATestNGClassWithBrokenDependencyMethod.class.name))
-            }
-            inSequence(sequence)
-
-            one(resultProcessor).completed(withParam(equalTo(3L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, equalTo(ResultType.SKIPPED))
-            }
-            inSequence(sequence)
-
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, nullValue())
-            }
-            inSequence(sequence)
-        }
+        then:
+        1 * resultProcessor.started({ it.id == 1} , _)
+        then:
+        1 * resultProcessor.started({ it.name == 'beforeMethod' && it.className == ATestNGClassWithBrokenSetupMethod.class.name }, _)
+
+        then:
+        1 * resultProcessor.failure(2, ATestNGClassWithBrokenSetupMethod.failure)
+        then:
+        1 * resultProcessor.completed(2, { it.resultType == ResultType.FAILURE })
 
+        then:
+        1 * resultProcessor.started({ it.name == 'test' && it.className == ATestNGClassWithBrokenSetupMethod.class.name }, _)
+        then:
+        1 * resultProcessor.completed(3, { it.resultType == ResultType.SKIPPED})
+
+        then:
+        1 * resultProcessor.completed(1, { it.resultType == null})
+        0 * resultProcessor._
+    }
+
+    void "executes test class with dependency method"() {
+        when:
         processor.startProcessing(resultProcessor);
         processor.processTestClass(testClass(ATestNGClassWithBrokenDependencyMethod.class));
         processor.stop();
-    }
 
-    @Test
-    public void canIncludeAndExcludeGroups() {
-        context.checking {
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal suite ->
-                assertThat(suite.name, equalTo('Gradle test'))
-                assertThat(suite.className, nullValue())
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.name, equalTo('group1'))
-                assertThat(test.className, equalTo(ATestNGClassWithGroups.class.name))
-            }
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.name, equalTo('group2'))
-                assertThat(test.className, equalTo(ATestNGClassWithGroups.class.name))
-            }
-            ignoring(resultProcessor).completed(withParam(anything()), withParam(anything()))
-        }
+        then:
+        1 * resultProcessor.started({ it.id == 1} , _)
+        then:
+        1 * resultProcessor.started({ it.name == 'beforeMethod' && it.className == ATestNGClassWithBrokenDependencyMethod.class.name }, _)
+
+        then:
+        1 * resultProcessor.failure(2, ATestNGClassWithBrokenDependencyMethod.failure)
+        then:
+        1 * resultProcessor.completed(2, { it.resultType == ResultType.FAILURE })
 
+        then:
+        1 * resultProcessor.started({ it.name == 'test' && it.className == ATestNGClassWithBrokenDependencyMethod.class.name }, _)
+        then:
+        1 * resultProcessor.completed(3, { it.resultType == ResultType.SKIPPED})
+
+        then:
+        1 * resultProcessor.completed(1, { it.resultType == null})
+        0 * resultProcessor._
+    }
+
+    void "includes and excludes groups"() {
+        given:
         options.includeGroups('group1', 'group2')
         options.excludeGroups('group3')
+
+        when:
         processor.startProcessing(resultProcessor);
         processor.processTestClass(testClass(ATestNGClassWithGroups.class));
         processor.stop();
-    }
-
-    @Test @Ignore
-    public void executesATestClassWithBrokenConstructor() {
-        context.checking {
-            Sequence sequence = context.sequence('seq')
-
-            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptorInternal test ->
-                assertThat(test.name, equalTo('initializationError'))
-                assertThat(test.className, equalTo(ATestNGClassWithBrokenConstructor.class.name))
-            }
-            inSequence(sequence)
-
-            one(resultProcessor).failure(1L, ATestNGClassWithBrokenConstructor.failure)
 
-            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
-            will { id, TestCompleteEvent event ->
-                assertThat(event.resultType, equalTo(ResultType.FAILURE))
-            }
-            inSequence(sequence)
-        }
+        then:
+        1 * resultProcessor.started({ it.id == 1} , _)
+        1 * resultProcessor.started({ it.name == 'group1' && it.className == ATestNGClassWithGroups.class.name }, _)
+        1 * resultProcessor.started({ it.name == 'group2' && it.className == ATestNGClassWithGroups.class.name }, _)
+        3 * resultProcessor.completed(_, _)
+        0 * resultProcessor._
+    }
 
+    @Ignore //not implemented yet
+    void "executes class with broken constructor"() {
+        when:
         processor.startProcessing(resultProcessor);
         processor.processTestClass(testClass(ATestNGClassWithBrokenConstructor.class));
         processor.stop();
+
+        then:
+        //below needs to revisited when we attempt to fix the problem
+        //e.g. decide what's the behavior we want in this scenario
+        1 * resultProcessor.started({ it.id == 1} , _)
+        1 * resultProcessor.started({ it.name == 'initializationError' && it.className == ATestNGClassWithBrokenConstructor.class.name }, _)
+        1 * resultProcessor.failure(1, ATestNGClassWithBrokenConstructor.failure)
+        1 * resultProcessor.completed(1, { it.resultType == ResultType.FAILURE})
+        0 * resultProcessor._
     }
 
-    @Test
-    public void failsEarlyForUnknownTestClass() {
+    void "fails early for unknown test class"() {
         processor.startProcessing(resultProcessor)
-        try {
-            processor.processTestClass(testClass('unknown'))
-            fail()
-        } catch (GradleException e) {
-            assertThat(e.message, equalTo('Could not load test class \'unknown\'.'))
-        }
+
+        when:
+        processor.processTestClass(testClass('unknown'))
+
+        then:
+        def ex = thrown(GradleException)
+        ex.message == "Could not load test class \'unknown\'."
     }
 
     private TestClassRunInfo testClass(Class<?> type) {
@@ -304,12 +193,7 @@ class TestNGTestClassProcessorTest {
     }
 
     private TestClassRunInfo testClass(String testClassName) {
-        TestClassRunInfo runInfo = context.mock(TestClassRunInfo.class)
-        context.checking {
-            allowing(runInfo).getTestClassName()
-            will(returnValue(testClassName))
-        }
-        return runInfo;
+        return { testClassName } as TestClassRunInfo
     }
 }
 
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.groovy
new file mode 100644
index 0000000..aac8f74
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.testng
+
+import org.gradle.api.tasks.testing.Test
+import org.gradle.api.tasks.testing.testng.TestNGOptions
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.testfixtures.ProjectBuilder
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+/**
+ * @author Szczepan Faber
+ */
+public class TestNGTestFrameworkTest extends Specification {
+
+    private project = new ProjectBuilder().build()
+    Test testTask = HelperUtil.createTask(Test, project)
+
+    void setup() {
+        project.ext.sourceCompatibility = "1.7"
+    }
+
+    void "initializes"() {
+        given:
+        project.ext.sourceCompatibility = "1.4"
+
+        when:
+        def framework = new TestNGTestFramework(testTask);
+
+        then:
+        framework.options.annotations == TestNGOptions.JAVADOC_ANNOTATIONS
+        framework.testTask == testTask
+        framework.options.projectDir == project.projectDir
+        framework.detector
+    }
+
+    void "initializes for newer java"() {
+        expect:
+        new TestNGTestFramework(testTask).options.annotations == TestNGOptions.JDK_ANNOTATIONS
+    }
+
+    void "creates test class processor"() {
+        when:
+        def framework = new TestNGTestFramework(testTask);
+        def processor = framework.getProcessorFactory().create(Mock(ServiceRegistry))
+
+        then:
+        framework.options.testResources.is(testTask.testSrcDirs)
+        processor instanceof TestNGTestClassProcessor
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.java
deleted file mode 100644
index f2d8c1c..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.tasks.testing.testng;
-
-import org.gradle.api.JavaVersion;
-import org.gradle.internal.Factory;
-import org.gradle.internal.id.IdGenerator;
-import org.gradle.internal.service.ServiceRegistry;
-import org.gradle.api.internal.tasks.testing.AbstractTestFrameworkTest;
-import org.gradle.api.internal.tasks.testing.TestClassProcessor;
-import org.gradle.api.tasks.testing.testng.TestNGOptions;
-import org.jmock.Expectations;
-import org.junit.Before;
-
-import java.io.File;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-/**
- * @author Tom Eyckmans
- */
-public class TestNGTestFrameworkTest extends AbstractTestFrameworkTest {
-
-    private TestNGTestFramework testNGTestFramework;
-    private TestNGOptions testngOptionsMock;
-    private IdGenerator<?> idGeneratorMock;
-    private ServiceRegistry serviceRegistry;
-
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-
-        testngOptionsMock = context.mock(TestNGOptions.class);
-        idGeneratorMock = context.mock(IdGenerator.class);
-        serviceRegistry = context.mock(ServiceRegistry.class);
-        final Factory<File> temporaryDirFactory = new Factory<File>() {
-            public File create() {
-                return temporaryDir;
-            }
-        };
-
-        final JavaVersion sourceCompatibility = JavaVersion.VERSION_1_5;
-        context.checking(new Expectations() {{
-            allowing(projectMock).getProjectDir(); will(returnValue(new File("projectDir")));
-            allowing(projectMock).property("sourceCompatibility"); will(returnValue(sourceCompatibility));
-            allowing(testMock).getTestClassesDir(); will(returnValue(testClassesDir));
-            allowing(testMock).getClasspath(); will(returnValue(classpathMock));
-            allowing(testMock).getTemporaryDir(); will(returnValue(temporaryDir));
-            allowing(testMock).getTemporaryDirFactory(); will(returnValue(temporaryDirFactory));
-        }});
-    }
-
-    @org.junit.Test
-    public void testInitialize() {
-        testNGTestFramework = new TestNGTestFramework(testMock);
-        setMocks();
-
-        assertNotNull(testNGTestFramework.getOptions());
-    }
-
-    @org.junit.Test
-    public void testCreatesTestProcessor() {
-        testNGTestFramework = new TestNGTestFramework(testMock);
-        setMocks();
-
-        context.checking(new Expectations() {{
-            allowing(testMock).getTestSrcDirs();    will(returnValue(testSrcDirs));
-            allowing(testMock).getTestReportDir();  will(returnValue(testReportDir));
-            allowing(testMock).getTestResultsDir(); will(returnValue(testResultsDir));
-            allowing(testMock).isTestReport();      will(returnValue(false));
-            allowing(serviceRegistry).get(IdGenerator.class); will(returnValue(idGeneratorMock));
-            one(testngOptionsMock).setTestResources(testSrcDirs);
-            one(testngOptionsMock).getSuites(temporaryDir);
-        }});
-
-        TestClassProcessor processor = testNGTestFramework.getProcessorFactory().create(serviceRegistry);
-        assertThat(processor, instanceOf(TestNGTestClassProcessor.class));
-    }
-
-    private void setMocks() {
-        testNGTestFramework.setOptions(testngOptionsMock);
-    }
-}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpecTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpecTest.groovy
index 53bcf57..8744feb 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpecTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpecTest.groovy
@@ -17,11 +17,11 @@ package org.gradle.api.java.archives.internal
 
 import org.gradle.api.Action
 import org.gradle.api.internal.file.FileResolver
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
+import org.gradle.api.java.archives.ManifestMergeDetails
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
-import org.gradle.api.java.archives.ManifestMergeDetails
 
 /**
  * @author Hans Dockter
@@ -32,7 +32,7 @@ class DefaultManifestMergeSpecTest extends Specification {
 
     DefaultManifestMergeSpec mergeSpec = new DefaultManifestMergeSpec()
     @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder()
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
     def from() {
         expect:
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestTest.groovy
index 6628942..9bf7616 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestTest.groovy
@@ -20,8 +20,8 @@ import org.apache.tools.ant.taskdefs.Manifest
 import org.apache.tools.ant.taskdefs.Manifest.Attribute
 import org.apache.tools.ant.taskdefs.Manifest.Section
 import org.gradle.api.internal.file.FileResolver
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -35,7 +35,7 @@ class DefaultManifestTest extends Specification {
     DefaultManifest gradleManifest = new DefaultManifest(fileResolver)
 
     @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder()
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
     def testInitWithFileReolver() {
         expect:
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ApplicationPluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ApplicationPluginTest.groovy
index 3874554..2851aaf 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ApplicationPluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ApplicationPluginTest.groovy
@@ -21,6 +21,7 @@ import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.JavaExec
 import org.gradle.api.tasks.SourceSet
 import org.gradle.api.tasks.bundling.Zip
+import org.gradle.api.tasks.bundling.Tar
 import org.gradle.util.HelperUtil
 import org.gradle.util.Matchers
 import spock.lang.Specification
@@ -85,6 +86,16 @@ class ApplicationPluginTest extends Specification {
         task.archiveName == "${project.applicationName}.zip"
     }
 
+    def "adds distTar task to project"() {
+        when:
+        plugin.apply(project)
+
+        then:
+        def task = project.tasks[ApplicationPlugin.TASK_DIST_TAR_NAME]
+        task instanceof Tar
+        task.archiveName == "${project.applicationName}.tar"
+    }
+
     public void "applicationName is configurable"() {
         when:
         plugin.apply(project)
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyBasePluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyBasePluginTest.groovy
index b7223ab..f096025 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyBasePluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyBasePluginTest.groovy
@@ -22,7 +22,9 @@ import org.gradle.api.reporting.ReportingExtension
 import org.gradle.api.tasks.compile.GroovyCompile
 import org.gradle.api.tasks.javadoc.Groovydoc
 import org.gradle.util.HelperUtil
+import org.junit.Before
 import org.junit.Test
+
 import static org.gradle.util.Matchers.dependsOn
 import static org.gradle.util.WrapUtil.toLinkedSet
 import static org.gradle.util.WrapUtil.toSet
@@ -35,34 +37,31 @@ import static org.junit.Assert.*
 
 class GroovyBasePluginTest {
     private final Project project = HelperUtil.createRootProject()
-    private final GroovyBasePlugin groovyBasePlugin = new GroovyBasePlugin()
 
-    @Test public void appliesTheJavaBasePluginToTheProject() {
-        groovyBasePlugin.apply(project)
+    @Before
+    void before() {
+        project.plugins.apply(GroovyBasePlugin)
+    }
 
+    @Test void appliesTheJavaBasePluginToTheProject() {
         assertTrue(project.getPlugins().hasPlugin(JavaBasePlugin));
     }
 
-    @Test public void addsGroovyConfigurationToTheProject() {
-        groovyBasePlugin.apply(project)
-        
-        def configuration = project.configurations.getByName(GroovyBasePlugin.GROOVY_CONFIGURATION_NAME)
+    @Test void addsGroovyConfigurationToTheProject() {
+        def configuration = project.configurations.findByName('groovy')
+        assertNotNull(configuration)
         assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(toSet()))
         assertFalse(configuration.visible)
-        assertFalse(configuration.transitive)
+        assertTrue(configuration.transitive)
     }
 
-    @Test public void appliesMappingsToNewSourceSet() {
-        groovyBasePlugin.apply(project)
-
+    @Test void appliesMappingsToNewSourceSet() {
         def sourceSet = project.sourceSets.add('custom')
         assertThat(sourceSet.groovy.displayName, equalTo("custom Groovy source"))
         assertThat(sourceSet.groovy.srcDirs, equalTo(toLinkedSet(project.file("src/custom/groovy"))))
     }
 
-    @Test public void addsCompileTaskToNewSourceSet() {
-        groovyBasePlugin.apply(project)
-        
+    @Test void addsCompileTaskToNewSourceSet() {
         project.sourceSets.add('custom')
 
         def task = project.tasks['compileCustomGroovy']
@@ -71,20 +70,34 @@ class GroovyBasePluginTest {
         assertThat(task, dependsOn('compileCustomJava'))
     }
 
-    @Test public void dependenciesOfJavaPluginTasksIncludeGroovyCompileTasks() {
-        groovyBasePlugin.apply(project)
-
+    @Test void dependenciesOfJavaPluginTasksIncludeGroovyCompileTasks() {
         project.sourceSets.add('custom')
         def task = project.tasks['customClasses']
         assertThat(task, dependsOn(hasItem('compileCustomGroovy')))
     }
    
-    @Test public void configuresAdditionalTasksDefinedByTheBuildScript() {
-        groovyBasePlugin.apply(project)
-
+    @Test void configuresAdditionalTasksDefinedByTheBuildScript() {
         def task = project.task('otherGroovydoc', type: Groovydoc)
         assertThat(task.destinationDir, equalTo(new File(project.docsDir, 'groovydoc')))
         assertThat(task.docTitle, equalTo(project.extensions.getByType(ReportingExtension).apiDocTitle))
         assertThat(task.windowTitle, equalTo(project.extensions.getByType(ReportingExtension).apiDocTitle))
     }
+
+    @Test void defaultsGroovyClasspathToGroovyConfigurationIfTheLatterIsNonEmpty() {
+        project.sourceSets.add('custom')
+        def configuration = project.configurations.groovy
+        project.dependencies {
+            groovy "org.codehaus.groovy:groovy-all:2.0.5"
+        }
+
+        def compileTask = project.tasks.compileCustomGroovy
+        assertSame(configuration, compileTask.groovyClasspath)
+
+        def groovydocTask = project.task('groovydoc', type: Groovydoc)
+        assertSame(configuration, groovydocTask.groovyClasspath)
+    }
+
+    // see GroovyBasePluginIntegrationTest
+    @Test void defaultsGroovyClasspathToInferredGroovyDependencyIfGroovyConfigurationIsEmpty() {
+    }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyPluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyPluginTest.groovy
index 64c7f7b..6e55872 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyPluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyPluginTest.groovy
@@ -21,10 +21,11 @@ import org.gradle.api.internal.artifacts.configurations.Configurations
 import org.gradle.api.reporting.ReportingExtension
 import org.gradle.api.tasks.compile.GroovyCompile
 import org.gradle.api.tasks.javadoc.Groovydoc
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.HelperUtil
-import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import org.junit.Test
+
 import static org.gradle.util.Matchers.dependsOn
 import static org.gradle.util.WrapUtil.toLinkedSet
 import static org.gradle.util.WrapUtil.toSet
@@ -36,7 +37,7 @@ import static org.junit.Assert.*
  */
 class GroovyPluginTest {
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder()
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     private final Project project = HelperUtil.createRootProject()
     private final GroovyPlugin groovyPlugin = new GroovyPlugin()
 
@@ -94,7 +95,7 @@ class GroovyPluginTest {
     @Test public void addsStandardTasksToTheProject() {
         groovyPlugin.apply(project)
 
-        project.sourceSets.main.groovy.srcDirs(tmpDir.getDir())
+        project.sourceSets.main.groovy.srcDirs(tmpDir.getTestDirectory())
         tmpDir.file("SomeFile.groovy").touch()
         def task = project.tasks[GroovyPlugin.GROOVYDOC_TASK_NAME]
         assertThat(task, instanceOf(Groovydoc.class))
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaBasePluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaBasePluginTest.groovy
index 1d1bbf7..98fa678 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaBasePluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaBasePluginTest.groovy
@@ -20,6 +20,7 @@ import org.gradle.api.Project
 import org.gradle.api.reporting.ReportingExtension
 import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.bundling.Jar
+import org.gradle.api.tasks.compile.JavaCompile
 import org.gradle.api.tasks.javadoc.Javadoc
 import org.gradle.api.tasks.testing.Test
 import org.gradle.internal.reflect.Instantiator
@@ -28,9 +29,9 @@ import org.gradle.util.Matchers
 import org.gradle.util.SetSystemProperties
 import org.junit.Rule
 import spock.lang.Specification
+
 import static org.gradle.util.Matchers.sameCollection
 import static org.gradle.util.WrapUtil.toLinkedSet
-import org.gradle.api.tasks.compile.JavaCompile
 
 /**
  * @author Hans Dockter
@@ -143,12 +144,32 @@ class JavaBasePluginTest extends Specification {
         test.workingDir == project.projectDir
         test.testResultsDir == project.testResultsDir
         test.testReportDir == project.testReportDir
+        test.testReport //by default (JUnit), the report is 'on'
 
         def javadoc = project.task('customJavadoc', type: Javadoc)
         javadoc.destinationDir == project.file("$project.docsDir/javadoc")
         javadoc.title == project.extensions.getByType(ReportingExtension).apiDocTitle
     }
 
+    void "configures test task for testNG"() {
+        given:
+        javaBasePlugin.apply(project)
+        def test = project.task('customTest', type: Test.class)
+
+        when:
+        test.useTestNG()
+
+        then:
+        assert test.testReport
+
+        when:
+        test.testReport = false
+        test.useTestNG()
+
+        then:
+        assert !test.testReport
+    }
+
     void appliesMappingsToCustomJarTasks() {
         when:
         javaBasePlugin.apply(project)
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaLibraryDistributionPluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaLibraryDistributionPluginTest.groovy
new file mode 100755
index 0000000..eb76389
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaLibraryDistributionPluginTest.groovy
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.plugins
+
+import org.gradle.api.Project
+import org.gradle.api.tasks.bundling.Zip
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class JavaLibraryDistributionPluginTest extends Specification {
+    private final Project project = HelperUtil.createRootProject();
+    private final JavaLibraryDistributionPlugin plugin = new JavaLibraryDistributionPlugin();
+
+    def "applies JavaPlugin and adds convention object with default values"() {
+        when:
+        plugin.apply(project)
+
+        then:
+        project.plugins.hasPlugin(JavaPlugin.class)
+        project.extensions.getByType(DistributionExtension.class) != null
+        project.distribution.name == project.name
+
+    }
+
+    def "adds distZip task to project"() {
+        when:
+        plugin.apply(project)
+
+        then:
+        def task = project.tasks[JavaLibraryDistributionPlugin.TASK_DIST_ZIP_NAME]
+        task instanceof Zip
+        task.archiveName == "${project.distribution.name}.zip"
+    }
+
+    public void "distribution name is configurable"() {
+        when:
+        plugin.apply(project)
+        project.distribution.name = "SuperApp";
+
+        then:
+        def distZipTask = project.tasks[JavaLibraryDistributionPlugin.TASK_DIST_ZIP_NAME]
+        distZipTask.archiveName == "SuperApp.zip"
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginConventionTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginConventionTest.groovy
index bd6e297..82c3ac2 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginConventionTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginConventionTest.groovy
@@ -22,13 +22,14 @@ import org.gradle.api.internal.project.DefaultProject
 import org.gradle.api.internal.tasks.DefaultSourceSetContainer
 import org.gradle.api.java.archives.internal.DefaultManifest
 import org.gradle.internal.reflect.Instantiator
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.HelperUtil
 import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertEquals
 import static org.junit.Assert.assertThat
@@ -43,7 +44,7 @@ class JavaPluginConventionTest {
     private JavaPluginConvention convention
 
     @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder()
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
     @Before public void setUp() {
         project.plugins.apply(ReportingBasePlugin)
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginTest.groovy
index bd00b14..8d7b3a8 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginTest.groovy
@@ -28,8 +28,8 @@ import org.gradle.api.tasks.SourceSet
 import org.gradle.api.tasks.bundling.Jar
 import org.gradle.api.tasks.compile.JavaCompile
 import org.gradle.api.tasks.javadoc.Javadoc
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.HelperUtil
-import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import org.junit.Test
 
@@ -44,7 +44,7 @@ import static org.junit.Assert.*
  */
 class JavaPluginTest {
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder()
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     private final Project project = HelperUtil.createRootProject()
     private final JavaPlugin javaPlugin = new JavaPlugin()
 
@@ -186,7 +186,7 @@ class JavaPluginTest {
         assertThat(task, instanceOf(DefaultTask))
         assertThat(task, dependsOn(JavaPlugin.TEST_TASK_NAME))
 
-        project.sourceSets.main.java.srcDirs(tmpDir.getDir())
+        project.sourceSets.main.java.srcDirs(tmpDir.getTestDirectory())
         tmpDir.file("SomeFile.java").touch()
         task = project.tasks[JavaPlugin.JAVADOC_TASK_NAME]
         assertThat(task, instanceOf(Javadoc))
@@ -225,25 +225,6 @@ class JavaPluginTest {
         assert task.classpath == project.sourceSets.test.runtimeClasspath
         assert task.testClassesDir == project.sourceSets.test.output.classesDir
         assert task.workingDir == project.projectDir
-        assert task.testReport //by default (JUnit), the report is 'on'
-    }
-
-    @Test void "configures test task for testNG"() {
-        javaPlugin.apply(project)
-        def task = project.tasks[JavaPlugin.TEST_TASK_NAME]
-
-        //when
-        task.useTestNG()
-
-        //then
-        assert !task.testReport //for TestNG, the report is 'off' by default for now
-
-        //when
-        task.testReport = true
-        task.useTestNG()
-
-        //then
-        assert task.testReport
     }
 
     @Test public void appliesMappingsToTasksAddedByTheBuildScript() {
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/github/GitHubDependenciesPluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/github/GitHubDependenciesPluginTest.groovy
deleted file mode 100644
index 829b00f..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/github/GitHubDependenciesPluginTest.groovy
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.plugins.github
-
-import org.gradle.api.Project
-import org.gradle.util.HelperUtil
-import spock.lang.Specification
-
-class GitHubDependenciesPluginTest extends Specification {
-
-    Project project = HelperUtil.createRootProject()
-
-    def "can apply plugin"() {
-        when:
-        project.apply(plugin: "github-dependencies")
-
-        then:
-        project.repositories.extensions.findByType(GitHubRepositoryHandlerExtension)
-
-        when:
-        project.repositories {
-            github.downloads("user1")
-            github {
-                downloads {
-                    user "user2"
-                    credentials {
-                        username "foo"
-                        password "bar"
-                    }
-                }
-            }
-        }
-
-        then:
-        project.repositories.size() == 2
-    }
-}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileTest.java
index d3896b5..65efbe0 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileTest.java
@@ -27,17 +27,12 @@ import org.gradle.util.JUnit4GroovyMockery;
 import org.hamcrest.core.IsNull;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.File;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.List;
 
-import static org.gradle.util.WrapUtil.toList;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -46,7 +41,6 @@ import static org.junit.Assert.assertTrue;
  */
 @RunWith(org.jmock.integration.junit4.JMock.class)
 public class GroovyCompileTest extends AbstractCompileTest {
-    static final List<File> TEST_GROOVY_CLASSPATH = toList(new File("groovy.jar"));
 
     private GroovyCompile testObj;
 
@@ -72,11 +66,11 @@ public class GroovyCompileTest extends AbstractCompileTest {
     }
 
     public void testExecute(final int numFilesCompiled) {
-        setUpMocksAndAttributes(testObj, TEST_GROOVY_CLASSPATH);
+        setUpMocksAndAttributes(testObj, false);
         context.checking(new Expectations(){{
             WorkResult result = context.mock(WorkResult.class);
 
-            one(groovyCompilerMock).execute(with(IsNull.<GroovyJavaJointCompileSpec>notNullValue()));
+            one(groovyCompilerMock).execute((GroovyJavaJointCompileSpec) with(IsNull.notNullValue()));
             will(returnValue(result));
             allowing(result).getDidWork();
             will(returnValue(numFilesCompiled > 0));
@@ -97,24 +91,19 @@ public class GroovyCompileTest extends AbstractCompileTest {
         assertFalse(testObj.getDidWork());
     }
 
-    @Test
-    public void testExecuteWithEmptyGroovyClasspath() {
-        setUpMocksAndAttributes(testObj, Collections.<File>emptyList());
-        try {
-            testObj.compile();
-        } catch (InvalidUserDataException e) {
-            return;
-        }
-        Assert.fail();
+    @Test(expected = InvalidUserDataException.class)
+    public void testMoansIfGroovyClasspathIsEmpty() {
+        setUpMocksAndAttributes(testObj, true);
+        testObj.compile();
     }
 
-    void setUpMocksAndAttributes(GroovyCompile compile, final List<File> groovyClasspath) {
+    void setUpMocksAndAttributes(GroovyCompile compile, final boolean groovyClasspathEmpty) {
         super.setUpMocksAndAttributes(compile);
 
         final FileCollection groovyClasspathCollection = context.mock(FileCollection.class);
         context.checking(new Expectations(){{
-            allowing(groovyClasspathCollection).getFiles();
-            will(returnValue(new LinkedHashSet<File>(groovyClasspath)));
+            allowing(groovyClasspathCollection).isEmpty();
+            will(returnValue(groovyClasspathEmpty));
         }});
 
         compile.setGroovyClasspath(groovyClasspathCollection);
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/JavaCompileTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/JavaCompileTest.java
index 0e60325..bd1afa7 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/JavaCompileTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/JavaCompileTest.java
@@ -17,11 +17,12 @@
 package org.gradle.api.tasks.compile;
 
 import org.gradle.api.internal.ConventionTask;
-import org.gradle.api.internal.tasks.compile.*;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.internal.tasks.compile.JavaCompileSpec;
 import org.gradle.api.tasks.WorkResult;
 import org.gradle.util.GFileUtils;
 import org.gradle.util.JUnit4GroovyMockery;
-import org.hamcrest.core.IsNull;
+import org.hamcrest.Matchers;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 import org.junit.Before;
@@ -32,7 +33,6 @@ import java.io.File;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import org.gradle.api.internal.tasks.compile.Compiler;
 
 /**
  * @author Hans Dockter
@@ -63,7 +63,7 @@ public class JavaCompileTest extends AbstractCompileTest {
         context.checking(new Expectations() {{
             WorkResult result = context.mock(WorkResult.class);
 
-            one(compilerMock).execute(with(IsNull.<JavaCompileSpec>notNullValue()));
+            one(compilerMock).execute((JavaCompileSpec) with(Matchers.notNullValue()));
             will(returnValue(result));
             allowing(result).getDidWork();
             will(returnValue(numFilesCompiled > 0));
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/javadoc/JavadocTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/javadoc/JavadocTest.java
index 3b5b20b..94c1737 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/javadoc/JavadocTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/javadoc/JavadocTest.java
@@ -21,12 +21,12 @@ import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.ConventionTask;
 import org.gradle.api.internal.file.collections.SimpleFileCollection;
 import org.gradle.api.tasks.AbstractConventionTaskTest;
-import org.gradle.external.javadoc.internal.JavadocExecHandleBuilder;
 import org.gradle.external.javadoc.StandardJavadocDocletOptions;
+import org.gradle.external.javadoc.internal.JavadocExecHandleBuilder;
 import org.gradle.process.internal.ExecAction;
 import org.gradle.process.internal.ExecException;
+import org.gradle.test.fixtures.file.TestFile;
 import org.gradle.util.GFileUtils;
-import org.gradle.util.TestFile;
 import org.gradle.util.WrapUtil;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -39,14 +39,15 @@ import java.io.File;
 import java.util.Set;
 
 import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
 
 @RunWith(org.jmock.integration.junit4.JMock.class)
 public class JavadocTest extends AbstractConventionTaskTest {
     private final JUnit4Mockery context = new JUnit4Mockery() {{
         setImposteriser(ClassImposteriser.INSTANCE);
     }};
-    private final TestFile testDir = tmpDir.getDir();
+    private final TestFile testDir = tmpDir.getTestDirectory();
     private final File destDir = new File(testDir, "dest");
     private final File srcDir = new File(testDir, "srcdir");
     private final Set<File> classpath = WrapUtil.toSet(new File("classpath"));
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestReportTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestReportTest.groovy
new file mode 100644
index 0000000..8db4e9d
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestReportTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks.testing
+
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.HelperUtil
+import org.junit.Rule
+import spock.lang.Specification
+
+class TestReportTest extends Specification {
+    @Rule TestNameTestDirectoryProvider tmp
+    def reportTask = HelperUtil.createTask(TestReport)
+
+    def "infers dependencies and results dirs from input tests"() {
+        def test1 = test("test1")
+        def test2 = test("test2")
+        def test3 = test("test3")
+
+        when:
+        reportTask.reportOn test1
+        reportTask.reportOn([[test2], test3])
+
+        then:
+        reportTask.testResultDirs.files as List == [test1, test2, test3].binResultsDir
+        reportTask.testResultDirs.buildDependencies.getDependencies(reportTask) == [test1, test2, test3] as Set
+    }
+
+    def "can attach result dirs"() {
+        def binDir = tmp.file("other")
+
+        when:
+        reportTask.reportOn binDir
+
+        then:
+        reportTask.testResultDirs.files as List == [binDir]
+    }
+
+    def test(String name) {
+        def test = HelperUtil.createTask(Test, HelperUtil.createRootProject(), name)
+        test.binResultsDir = tmp.file(name)
+        return test
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTaskSpec.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTaskSpec.groovy
new file mode 100644
index 0000000..4ba1207
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTaskSpec.groovy
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.tasks.testing
+
+import org.gradle.api.internal.tasks.testing.TestFramework
+import org.gradle.api.internal.tasks.testing.TestResultProcessor
+import org.gradle.api.internal.tasks.testing.detection.TestExecuter
+import org.gradle.api.internal.tasks.testing.junit.report.TestReporter
+import org.gradle.listener.ListenerBroadcast
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 12/7/12
+ */
+class TestTaskSpec extends Specification {
+
+    private testExecuter = Mock(TestExecuter)
+    private testFramework = Mock(TestFramework)
+    private testListenerBroadcaster = Mock(ListenerBroadcast)
+    private testOutputListenerBroadcaster = Mock(ListenerBroadcast)
+
+    private task = HelperUtil.createTask(Test, [testExecuter: testExecuter, testFramework: testFramework,
+            testListenerBroadcaster: testListenerBroadcaster, testOutputListenerBroadcaster: testOutputListenerBroadcaster])
+
+    public setup(){
+        task.setTestReporter(Mock(TestReporter))
+        task.setBinResultsDir(task.project.file('build/test-results'))
+    }
+
+    def "adds listeners and removes after execution"() {
+        when:
+        task.executeTests()
+
+        then:
+        3 * testListenerBroadcaster.add(_)
+        2 * testOutputListenerBroadcaster.add(_)
+
+        then:
+        1 * testExecuter.execute(task, _ as TestResultProcessor)
+
+        then:
+        1 * testListenerBroadcaster.removeAll({it.size() == 3})
+        1 * testOutputListenerBroadcaster.removeAll({it.size() == 2})
+    }
+
+    def "removes listeners even if execution fails"() {
+        testExecuter.execute(task, _ as TestResultProcessor) >> { throw new RuntimeException("Boo!")}
+
+        when:
+        task.executeTests()
+
+        then:
+        def ex = thrown(RuntimeException)
+        ex.message == "Boo!"
+
+        and:
+        1 * testListenerBroadcaster.removeAll({it.size() == 3})
+        1 * testOutputListenerBroadcaster.removeAll({it.size() == 2})
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTest.java
index baf1884..5b69a28 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTest.java
@@ -31,10 +31,11 @@ import org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory;
 import org.gradle.api.internal.tasks.testing.detection.TestExecuter;
 import org.gradle.api.internal.tasks.testing.detection.TestFrameworkDetector;
 import org.gradle.api.internal.tasks.testing.junit.JUnitTestFramework;
+import org.gradle.api.internal.tasks.testing.junit.report.TestReporter;
+import org.gradle.api.internal.tasks.testing.junit.result.TestResultsProvider;
 import org.gradle.api.internal.tasks.testing.results.TestListenerAdapter;
 import org.gradle.api.tasks.AbstractConventionTaskTest;
 import org.gradle.process.internal.WorkerProcessBuilder;
-import org.gradle.logging.internal.OutputEventListener;
 import org.gradle.util.GFileUtils;
 import org.gradle.util.HelperUtil;
 import org.gradle.util.TestClosure;
@@ -72,6 +73,7 @@ public class TestTest extends AbstractConventionTaskTest {
 
     private File classesDir;
     private File resultsDir;
+    private File binResultsDir;
     private File reportDir;
 
     private JUnit4Mockery context = new JUnit4Mockery() {{
@@ -80,18 +82,17 @@ public class TestTest extends AbstractConventionTaskTest {
 
     TestFramework testFrameworkMock = context.mock(TestFramework.class);
     TestExecuter testExecuterMock = context.mock(TestExecuter.class);
-    OutputEventListener outputListenerMock = context.mock(OutputEventListener.class);
     private FileCollection classpathMock = new SimpleFileCollection(new File("classpath"));
     private Test test;
 
     @Before
     public void setUp() {
-        File rootDir = getProject().getProjectDir();
-        classesDir = new File(rootDir, "testClassesDir");
+        classesDir = tmpDir.createDir("classes");
         File classfile = new File(classesDir, "FileTest.class");
         GFileUtils.touch(classfile);
-        resultsDir = new File(rootDir, "resultDir");
-        reportDir = new File(rootDir, "report/tests");
+        resultsDir = tmpDir.createDir("testResults");
+        binResultsDir = tmpDir.createDir("binResults");
+        reportDir = tmpDir.createDir("report");
 
         test = createTask(Test.class);
     }
@@ -121,6 +122,20 @@ public class TestTest extends AbstractConventionTaskTest {
     }
 
     @org.junit.Test
+    public void generatesReport() {
+        configureTask();
+        expectTestsExecuted();
+
+        final TestReporter testReporter = context.mock(TestReporter.class);
+        test.setTestReporter(testReporter);
+        context.checking(new Expectations() {{
+            one(testReporter).generateReport(with(any(TestResultsProvider.class)), with(equal(reportDir)));
+        }});
+
+        test.executeTests();
+    }
+
+    @org.junit.Test
     public void testExecuteWithTestFailuresAndStopAtFailures() {
         configureTask();
         expectTestsFail();
@@ -162,9 +177,6 @@ public class TestTest extends AbstractConventionTaskTest {
                 return null;
             }
 
-            public void report() {
-            }
-
             public TestFrameworkOptions getOptions() {
                 return null;
             }
@@ -313,7 +325,6 @@ public class TestTest extends AbstractConventionTaskTest {
         expectOptionsBuilt();
         context.checking(new Expectations() {{
             one(testExecuterMock).execute(with(sameInstance(test)), with(notNullValue(TestListenerAdapter.class)));
-            one(testFrameworkMock).report();
         }});
     }
 
@@ -348,8 +359,6 @@ public class TestTest extends AbstractConventionTaskTest {
                     return null;
                 }
             });
-
-            one(testFrameworkMock).report();
         }});
     }
 
@@ -359,6 +368,7 @@ public class TestTest extends AbstractConventionTaskTest {
 
         test.setTestClassesDir(classesDir);
         test.setTestResultsDir(resultsDir);
+        test.setBinResultsDir(binResultsDir);
         test.setTestReportDir(reportDir);
         test.setClasspath(classpathMock);
         test.setTestSrcDirs(Collections.<File>emptyList());
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/wrapper/WrapperTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/wrapper/WrapperTest.java
index 4b6d6c6..508b20c 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/wrapper/WrapperTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/wrapper/WrapperTest.java
@@ -18,7 +18,11 @@ package org.gradle.api.tasks.wrapper;
 
 import org.gradle.api.internal.AbstractTask;
 import org.gradle.api.tasks.AbstractTaskTest;
-import org.gradle.util.*;
+import org.gradle.test.fixtures.file.TestFile;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
+import org.gradle.util.GUtil;
+import org.gradle.util.GradleVersion;
+import org.gradle.util.WrapUtil;
 import org.gradle.wrapper.GradleWrapperMain;
 import org.gradle.wrapper.WrapperExecutor;
 import org.junit.Before;
@@ -42,7 +46,7 @@ public class WrapperTest extends AbstractTaskTest {
     private TestFile expectedTargetWrapperJar;
     private File expectedTargetWrapperProperties;
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
     @Before
     public void setUp() {
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriterTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriterTest.groovy
index fc4b9dd..bf830ef 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriterTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriterTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.external.javadoc.internal
 
 import org.gradle.external.javadoc.JavadocOptionFileOption
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -25,7 +25,7 @@ import static org.gradle.util.TextUtil.toPlatformLineSeparators
 
 class JavadocOptionFileWriterTest extends Specification {
 
-    @Rule TemporaryFolder temporaryFolder
+    @Rule TestNameTestDirectoryProvider temporaryFolder
 
     JavadocOptionFile optionfile = Mock()
     JavadocOptionFileWriter javadocOptionFileWriter = new JavadocOptionFileWriter(optionfile)
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/PublishingExtension.java b/subprojects/publish/src/main/java/org/gradle/api/publish/PublishingExtension.java
index d386ba7..90854bb 100644
--- a/subprojects/publish/src/main/java/org/gradle/api/publish/PublishingExtension.java
+++ b/subprojects/publish/src/main/java/org/gradle/api/publish/PublishingExtension.java
@@ -91,8 +91,7 @@ public interface PublishingExtension {
      * The ability to create different kinds of publications is provided by different plugins. The “publishing” plugin itself does not provide a way
      * to create publications.
      * <p>
-     * Please see {@link org.gradle.api.publish.ivy.IvyPublication} for information on publishing in the Ivy format.
-     * At this time it is not possible to publish in the Maven format with this mechanism.
+     * Please see {@link org.gradle.api.publish.ivy.IvyPublication} and {@link org.gradle.api.publish.maven.MavenPublication} for more information on publishing in these specific formats.
      *
      * @param configure The action or closure to configure the publications with.
      */
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/internal/PublishOperation.java b/subprojects/publish/src/main/java/org/gradle/api/publish/internal/PublishOperation.java
new file mode 100644
index 0000000..bd94b3d
--- /dev/null
+++ b/subprojects/publish/src/main/java/org/gradle/api/publish/internal/PublishOperation.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.internal;
+
+import org.gradle.api.artifacts.PublishException;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+import org.gradle.api.publish.Publication;
+
+public abstract class PublishOperation implements Runnable {
+
+    private final Publication publication;
+    private final ArtifactRepository repository;
+
+    protected PublishOperation(Publication publication, ArtifactRepository repository) {
+        this.publication = publication;
+        this.repository = repository;
+    }
+
+    protected abstract void publish() throws Exception;
+
+    public void run() {
+        try {
+            publish();
+        } catch (Exception e) {
+            throw new PublishException(String.format("Failed to publish publication '%s' to repository '%s'", publication.getName(), repository.getName()), e);
+        }
+    }
+}
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/plugins/PublishingPlugin.java b/subprojects/publish/src/main/java/org/gradle/api/publish/plugins/PublishingPlugin.java
index e01ae7e..4e00dda 100644
--- a/subprojects/publish/src/main/java/org/gradle/api/publish/plugins/PublishingPlugin.java
+++ b/subprojects/publish/src/main/java/org/gradle/api/publish/plugins/PublishingPlugin.java
@@ -25,7 +25,6 @@ import org.gradle.api.publish.PublicationContainer;
 import org.gradle.api.publish.PublishingExtension;
 import org.gradle.api.publish.internal.DefaultPublicationContainer;
 import org.gradle.api.publish.internal.DefaultPublishingExtension;
-import org.gradle.internal.Factory;
 import org.gradle.internal.reflect.Instantiator;
 
 import javax.inject.Inject;
@@ -41,16 +40,16 @@ public class PublishingPlugin implements Plugin<Project> {
     public static final String PUBLISH_LIFECYCLE_TASK_NAME = "publish";
 
     private final Instantiator instantiator;
-    private final Factory<ArtifactPublicationServices> artifactPublicationServicesFactory;
+    private final ArtifactPublicationServices publicationServices;
 
     @Inject
-    public PublishingPlugin(Factory<ArtifactPublicationServices> artifactPublicationServicesFactory, Instantiator instantiator) {
-        this.artifactPublicationServicesFactory = artifactPublicationServicesFactory;
+    public PublishingPlugin(ArtifactPublicationServices publicationServices, Instantiator instantiator) {
+        this.publicationServices = publicationServices;
         this.instantiator = instantiator;
     }
 
     public void apply(Project project) {
-        RepositoryHandler repositories = artifactPublicationServicesFactory.create().getRepositoryHandler();
+        RepositoryHandler repositories = publicationServices.createRepositoryHandler();
         PublicationContainer publications = instantiator.newInstance(DefaultPublicationContainer.class, instantiator);
         project.getExtensions().create(PublishingExtension.NAME, DefaultPublishingExtension.class, repositories, publications);
 
diff --git a/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/DefaultPublicationContainerTest.groovy b/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/DefaultPublicationContainerTest.groovy
index f9e9d0c..566d5ff 100644
--- a/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/DefaultPublicationContainerTest.groovy
+++ b/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/DefaultPublicationContainerTest.groovy
@@ -21,8 +21,6 @@ import org.gradle.api.publish.Publication
 import org.gradle.api.publish.PublicationContainer
 import org.gradle.api.publish.UnknownPublicationException
 import org.gradle.internal.reflect.Instantiator
-import org.gradle.util.ConfigureUtil
-import spock.lang.Ignore
 import spock.lang.Specification
 
 class DefaultPublicationContainerTest extends Specification {
@@ -45,22 +43,6 @@ class DefaultPublicationContainerTest extends Specification {
         e.message == "Publication with name 'notHere' not found"
     }
 
-    @Ignore("This throws an MME, which is not as nice for the user")
-    def "right exception is thrown on unknown access as method"() {
-        given:
-        container.add(publication("foo"))
-
-        expect:
-        container.foo { } instanceof Publication
-
-        when:
-        ConfigureUtil.configure({ notHere { } }, container)
-
-        then:
-        def e = thrown(UnknownPublicationException)
-        e.message == "Publication with name 'notHere' not found"
-    }
-
     Publication publication(String name) {
         new Publication() {
             String getName() {
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/ScalaBasePluginIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/ScalaBasePluginIntegrationTest.groovy
new file mode 100644
index 0000000..a770434
--- /dev/null
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/ScalaBasePluginIntegrationTest.groovy
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.scala
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class ScalaBasePluginIntegrationTest extends AbstractIntegrationSpec {
+    def "defaults scalaClasspath to inferred Scala compiler dependency if scalaTools configuration is empty"() {
+        file("build.gradle") << """
+apply plugin: "scala-base"
+
+sourceSets {
+    custom
+}
+
+repositories {
+    mavenCentral()
+}
+dependencies {
+    customCompile "org.scala-lang:scala-library:2.9.2"
+}
+
+task scaladoc(type: ScalaDoc) {
+    classpath = sourceSets.custom.runtimeClasspath
+}
+
+task verify << {
+    assert compileCustomScala.scalaClasspath.files.any { it.name == "scala-compiler-2.9.2.jar" }
+    assert scalaCustomConsole.classpath.files.any { it.name == "scala-compiler-2.9.2.jar" }
+    assert scaladoc.scalaClasspath.files.any { it.name == "scala-compiler-2.9.2.jar" }
+}
+"""
+
+        expect:
+        succeeds("verify")
+    }
+
+    def "defaults scalaClasspath to (empty) scalaTools configuration if Scala compiler dependency isn't found on class path"() {
+        file("build.gradle") << """
+apply plugin: "scala-base"
+
+sourceSets {
+    custom
+}
+
+repositories {
+    mavenCentral()
+}
+
+task scaladoc(type: ScalaDoc)
+
+task verify << {
+    assert compileCustomScala.scalaClasspath.is(configurations.scalaTools)
+    assert scalaCustomConsole.classpath.is(configurations.scalaTools)
+    assert scaladoc.scalaClasspath.is(configurations.scalaTools)
+}
+"""
+
+        expect:
+        succeeds("verify")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntForkingScalaCompilerIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntForkingScalaCompilerIntegrationTest.groovy
index 22cfac8..89356e9 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntForkingScalaCompilerIntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntForkingScalaCompilerIntegrationTest.groovy
@@ -21,7 +21,7 @@ import org.gradle.integtests.fixtures.TargetVersions
 @TargetVersions(["2.8.2", "2.9.2"])
 class AntForkingScalaCompilerIntegrationTest extends BasicScalaCompilerIntegrationTest {
     def setup() {
-        distribution.requireIsolatedDaemons()
+        executer.requireIsolatedDaemons()
     }
 
     String compilerConfiguration() {
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntInProcessScalaCompilerIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntInProcessScalaCompilerIntegrationTest.groovy
index c387bbc..667be28 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntInProcessScalaCompilerIntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntInProcessScalaCompilerIntegrationTest.groovy
@@ -21,7 +21,7 @@ import org.gradle.integtests.fixtures.TargetVersions
 @TargetVersions(["2.8.2", "2.9.2"])
 class AntInProcessScalaCompilerIntegrationTest extends BasicScalaCompilerIntegrationTest {
     def setup() {
-        distribution.requireIsolatedDaemons()
+        executer.requireIsolatedDaemons()
     }
 
     String compilerConfiguration() {
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/BasicScalaCompilerIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/BasicScalaCompilerIntegrationTest.groovy
index 6181510..b3ec240 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/BasicScalaCompilerIntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/BasicScalaCompilerIntegrationTest.groovy
@@ -42,7 +42,7 @@ DeprecationLogger.whileDisabled {
         file("build/classes/main/compile/test/Person.class").exists()
     }
 
-    def compileBadCodeBreaksTheBuild() {
+    def compileBadCode() {
         given:
         badCode()
 
@@ -146,7 +146,6 @@ repositories {
 }
 
 dependencies {
-    scalaTools "org.scala-lang:scala-compiler:$version"
     compile "org.scala-lang:scala-library:$version"
     compile localGroovy()
 }
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest.groovy
index b5a190a..f616c50 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest.groovy
@@ -15,40 +15,69 @@
  */
 package org.gradle.scala.compile
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.TestResources
-import org.gradle.integtests.fixtures.ExecutionFailure
-
 import org.junit.Rule
-import org.junit.Test
+import spock.lang.Ignore
+import spock.lang.Issue
+
+class IncrementalScalaCompileIntegrationTest extends AbstractIntegrationSpec {
 
-class IncrementalScalaCompileIntegrationTest {
-    @Rule public final GradleDistribution distribution = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final TestResources resources = new TestResources()
+    @Rule TestResources resources = new TestResources()
 
-    @Test
-    public void recompilesSourceWhenPropertiesChange() {
-        executer.withTasks('compileScala').run().assertTasksSkipped(':compileJava')
+    def recompilesSourceWhenPropertiesChange() {
+        expect:
+        run('compileScala').assertTasksSkipped(':compileJava')
 
-        distribution.testFile('build.gradle').text += '''
+        when:
+        file('build.gradle').text += '''
             compileScala.options.debug = false
 '''
+        then:
+        run('compileScala').assertTasksSkipped(':compileJava')
+
+        run('compileScala').assertTasksSkipped(':compileJava', ':compileScala')
+    }
+
+    def recompilesDependentClasses() {
+        given:
+        run("classes")
 
-        executer.withTasks('compileScala').run().assertTasksSkipped(':compileJava')
+        when: // Update interface, compile should fail
+        file('src/main/scala/IPerson.scala').assertIsFile().copyFrom(file('NewIPerson.scala'))
 
-        executer.withTasks('compileScala').run().assertTasksSkipped(':compileJava', ':compileScala')
+        then:
+        runAndFail("classes").assertHasDescription("Execution failed for task ':compileScala'.")
     }
 
-    @Test
-    public void recompilesDependentClasses() {
-        executer.withTasks("classes").run();
+    @Issue("GRADLE-2548")
+    @Ignore
+    def recompilesScalaWhenJavaChanges() {
+        file("build.gradle") << """
+            apply plugin: 'scala'
+
+            repositories {
+                mavenCentral()
+            }
+
+            dependencies {
+                compile 'org.scala-lang:scala-library:2.9.2'
+            }
+        """
+
+        file("src/main/java/Person.java") << "public interface Person { String getName(); }"
+
+        file("src/main/scala/DefaultPerson.scala") << """class DefaultPerson(name: String) extends Person {
+    def getName(): String = name
+}"""
+        when:
+        run('classes') //makes everything up-to-date
 
-        // Update interface, compile should fail
-        distribution.testFile('src/main/scala/IPerson.scala').assertIsFile().copyFrom(distribution.testFile('NewIPerson.scala'))
+        //change the java interface
+        file("src/main/java/Person.java").text = "public interface Person { String fooBar(); }"
 
-        ExecutionFailure failure = executer.withTasks("classes").runWithFailure();
-        failure.assertHasDescription("Execution failed for task ':compileScala'.");
+        then:
+        //the build should fail because the interface the scala class needs has changed
+        runAndFail("classes").assertHasDescription("Execution failed for task ':compileScala'.")
     }
 }
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/ZincScalaCompilerIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/ZincScalaCompilerIntegrationTest.groovy
index 8f7ce4e..4124da4 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/ZincScalaCompilerIntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/ZincScalaCompilerIntegrationTest.groovy
@@ -32,7 +32,6 @@ repositories {
 }
 
 dependencies {
-    scalaTools "org.scala-lang:scala-compiler:2.9.2"
     compile "org.scala-lang:scala-library:2.9.2"
 }
 
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntForkingScalaCompilerJdk6IntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntForkingScalaCompilerJdk6IntegrationTest.groovy
index cfe8a4b..b501351 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntForkingScalaCompilerJdk6IntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntForkingScalaCompilerJdk6IntegrationTest.groovy
@@ -22,7 +22,7 @@ import org.gradle.scala.compile.BasicScalaCompilerIntegrationTest
 @TargetVersions(["2.10.0-RC1"])
 class AntForkingScalaCompilerJdk6IntegrationTest extends BasicScalaCompilerIntegrationTest {
     def setup() {
-        distribution.requireIsolatedDaemons()
+        executer.requireIsolatedDaemons()
     }
 
     String compilerConfiguration() {
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntInProcessScalaCompilerJdk6IntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntInProcessScalaCompilerJdk6IntegrationTest.groovy
index b1472d1..490e3fa 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntInProcessScalaCompilerJdk6IntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntInProcessScalaCompilerJdk6IntegrationTest.groovy
@@ -22,7 +22,7 @@ import org.gradle.scala.compile.BasicScalaCompilerIntegrationTest
 @TargetVersions(["2.10.0-RC1"])
 class AntInProcessScalaCompilerJdk6IntegrationTest extends BasicScalaCompilerIntegrationTest {
     def setup() {
-        distribution.requireIsolatedDaemons()
+        executer.requireIsolatedDaemons()
     }
 
     String compilerConfiguration() {
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/environment/JreJavaHomeScalaIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/environment/JreJavaHomeScalaIntegrationTest.groovy
index 7fa9d6d..bcccb82 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/environment/JreJavaHomeScalaIntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/environment/JreJavaHomeScalaIntegrationTest.groovy
@@ -25,7 +25,6 @@ import spock.lang.Unroll
 
 class JreJavaHomeScalaIntegrationTest extends AbstractIntegrationSpec {
 
-
     @IgnoreIf({ AvailableJavaHomes.bestJre == null})
     @Unroll
     def "scala java cross compilation works in forking mode = #forkMode when JAVA_HOME is set to JRE"() {
@@ -49,9 +48,7 @@ class JreJavaHomeScalaIntegrationTest extends AbstractIntegrationSpec {
                     }
 
                     dependencies {
-                        scalaTools 'org.scala-lang:scala-compiler:2.9.2'
-                        scalaTools 'org.scala-lang:scala-library:2.9.2'
-                        compile    'org.scala-lang:scala-library:2.9.2'
+                        compile 'org.scala-lang:scala-library:2.9.2'
                     }
 
                     compileScala{
@@ -80,12 +77,10 @@ class JreJavaHomeScalaIntegrationTest extends AbstractIntegrationSpec {
                     }
 
                     dependencies {
-                        scalaTools 'org.scala-lang:scala-compiler:2.9.2'
-                        scalaTools 'org.scala-lang:scala-library:2.9.2'
-                        compile    'org.scala-lang:scala-library:2.9.2'
+                        compile 'org.scala-lang:scala-library:2.9.2'
                     }
                     """
-        def envVars = System.getenv().findAll { it.key != 'JAVA_HOME' || it.key != 'Path'}
+        def envVars = System.getenv().findAll { !(it.key in ['GRADLE_OPTS', 'JAVA_HOME', 'Path']) }
         envVars.put("Path", "C:\\Windows\\System32")
         when:
         executer.withEnvironmentVars(envVars).withTasks("compileScala").run()
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/test/ScalaTestIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/test/ScalaTestIntegrationTest.groovy
new file mode 100644
index 0000000..45550cc
--- /dev/null
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/test/ScalaTestIntegrationTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.scala.test
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.Rule
+
+class ScalaTestIntegrationTest extends AbstractIntegrationSpec {
+    @Rule TestResources resources
+    
+    def executesTestsWithMultiLineDescriptions() {
+        file("build.gradle") << """
+apply plugin: 'scala'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile "org.scala-lang:scala-library:2.9.2"
+    testCompile "org.scalatest:scalatest_2.9.2:1.8"
+    testCompile "junit:junit:4.11"
+}
+        """
+
+        when:
+        file("src/test/scala/MultiLineNameTest.scala") << """
+package org.gradle
+
+import org.scalatest.FunSuite
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+ at RunWith(classOf[JUnitRunner])
+class MultiLineSuite extends FunSuite {
+    test("This test method name\\nspans many\\nlines") {
+        assert(1 === 1)
+    }
+}
+        """
+
+        then:
+        succeeds("test")
+
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(testDirectory)
+        result.assertTestClassesExecuted("org.gradle.MultiLineSuite")
+	    result.testClass("org.gradle.MultiLineSuite").assertTestPassed("This test method name\nspans many\nlines")
+    }
+}
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/build.gradle b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/build.gradle
index bbc0f8b..3f70c75 100644
--- a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/build.gradle
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/build.gradle
@@ -5,6 +5,5 @@ repositories {
 }
 
 dependencies {
-    scalaTools 'org.scala-lang:scala-compiler:2.9.2'
     compile 'org.scala-lang:scala-library:2.9.2'
 }
\ No newline at end of file
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
index 2088e30..4d9720c 100644
--- a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
@@ -5,6 +5,5 @@ repositories {
 }
 
 dependencies {
-    scalaTools 'org.scala-lang:scala-compiler:2.9.2'
     compile 'org.scala-lang:scala-library:2.9.2'
 }
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/build.gradle b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/build.gradle
index c2d8f8b..b14f011 100644
--- a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/build.gradle
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/build.gradle
@@ -6,7 +6,6 @@ subprojects {
     }
 
     dependencies {
-        scalaTools "org.scala-lang:scala-compiler:$scalaVersion"
         compile "org.scala-lang:scala-library:$scalaVersion"
     }
 
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesJavaCodeIncrementally/build.gradle b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesJavaCodeIncrementally/build.gradle
index f1770d8..00c1d84 100644
--- a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesJavaCodeIncrementally/build.gradle
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesJavaCodeIncrementally/build.gradle
@@ -5,7 +5,6 @@ repositories {
 }
 
 dependencies {
-    scalaTools "org.scala-lang:scala-compiler:$scalaVersion"
     compile "org.scala-lang:scala-library:$scalaVersion"
 }
 
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesScalaCodeIncrementally/build.gradle b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesScalaCodeIncrementally/build.gradle
index f1770d8..00c1d84 100644
--- a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesScalaCodeIncrementally/build.gradle
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesScalaCodeIncrementally/build.gradle
@@ -5,7 +5,6 @@ repositories {
 }
 
 dependencies {
-    scalaTools "org.scala-lang:scala-compiler:$scalaVersion"
     compile "org.scala-lang:scala-library:$scalaVersion"
 }
 
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaBasePlugin.groovy b/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaBasePlugin.groovy
index 929a270..1dd6118 100644
--- a/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaBasePlugin.groovy
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaBasePlugin.groovy
@@ -17,7 +17,10 @@ package org.gradle.api.plugins.scala;
 
 import org.gradle.api.Plugin
 import org.gradle.api.Project
+import org.gradle.api.file.FileCollection
 import org.gradle.api.file.FileTreeElement
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
+import org.gradle.api.internal.file.FileResolver
 import org.gradle.api.internal.tasks.DefaultScalaSourceSet
 import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.api.plugins.JavaPluginConvention
@@ -27,12 +30,27 @@ import org.gradle.api.tasks.scala.ScalaCompile
 import org.gradle.api.tasks.scala.ScalaDoc
 import org.gradle.api.tasks.JavaExec
 
+import javax.inject.Inject
+import java.util.regex.Pattern
+
 class ScalaBasePlugin implements Plugin<Project> {
     // public configurations
     static final String SCALA_TOOLS_CONFIGURATION_NAME = "scalaTools"
     static final String ZINC_CONFIGURATION_NAME = "zinc"
 
+    private static final String DEFAULT_ZINC_VERSION = "0.2.0"
+    private static final Pattern SCALA_LIBRARY_JAR_PATTERN = Pattern.compile("scala-library-(\\d.*).jar")
+
+    private Project project
+    private final FileResolver fileResolver
+
+    @Inject
+    ScalaBasePlugin(FileResolver fileResolver) {
+        this.fileResolver = fileResolver
+    }
+
     void apply(Project project) {
+        this.project = project
         def javaPlugin = project.plugins.apply(JavaBasePlugin.class)
 
         project.configurations.add(SCALA_TOOLS_CONFIGURATION_NAME)
@@ -42,25 +60,25 @@ class ScalaBasePlugin implements Plugin<Project> {
                 .setVisible(false)
                 .setDescription("The Zinc incremental compiler to be used for this Scala project.")
 
-        configureCompileDefaults(project, javaPlugin)
-        configureSourceSetDefaults(project, javaPlugin)
-        configureScaladoc(project)
+        configureCompileDefaults()
+        configureSourceSetDefaults(javaPlugin)
+        configureScaladoc()
     }
 
-    private void configureSourceSetDefaults(Project project, JavaBasePlugin javaPlugin) {
+    private void configureSourceSetDefaults(JavaBasePlugin javaPlugin) {
         project.convention.getPlugin(JavaPluginConvention.class).sourceSets.all { SourceSet sourceSet ->
-            sourceSet.convention.plugins.scala = new DefaultScalaSourceSet(sourceSet.displayName, project.fileResolver)
-            sourceSet.scala.srcDir { project.file("src/$sourceSet.name/scala")}
+            sourceSet.convention.plugins.scala = new DefaultScalaSourceSet(sourceSet.displayName, fileResolver)
+            sourceSet.scala.srcDir { project.file("src/$sourceSet.name/scala") }
             sourceSet.allJava.source(sourceSet.scala)
             sourceSet.allSource.source(sourceSet.scala)
             sourceSet.resources.filter.exclude { FileTreeElement element -> sourceSet.scala.contains(element.file) }
 
-            configureScalaCompile(project, javaPlugin, sourceSet)
-            configureScalaConsole(project, sourceSet)
+            configureScalaCompile(javaPlugin, sourceSet)
+            configureScalaConsole(sourceSet)
         }
     }
 
-    private void configureScalaCompile(Project project, JavaBasePlugin javaPlugin, SourceSet sourceSet) {
+    private void configureScalaCompile(JavaBasePlugin javaPlugin, SourceSet sourceSet) {
         def taskName = sourceSet.getCompileTaskName('scala')
         def scalaCompile = project.tasks.add(taskName, ScalaCompile)
         scalaCompile.dependsOn sourceSet.compileJavaTaskName
@@ -84,26 +102,26 @@ class ScalaBasePlugin implements Plugin<Project> {
         }
     }
 
-    private void configureScalaConsole(Project project, SourceSet sourceSet) {
+    private void configureScalaConsole(SourceSet sourceSet) {
         def taskName = sourceSet.getTaskName("scala", "Console")
         def scalaConsole = project.tasks.add(taskName, JavaExec)
         scalaConsole.dependsOn(sourceSet.runtimeClasspath)
         scalaConsole.description = "Starts a Scala REPL with the $sourceSet.name runtime class path."
         scalaConsole.main = "scala.tools.nsc.MainGenericRunner"
-        scalaConsole.classpath = project.configurations[SCALA_TOOLS_CONFIGURATION_NAME]
+        scalaConsole.conventionMapping.classpath = { getScalaClasspath(sourceSet.runtimeClasspath) }
         scalaConsole.systemProperty("scala.usejavacp", true)
         scalaConsole.standardInput = System.in
         scalaConsole.conventionMapping.jvmArgs = { ["-classpath", sourceSet.runtimeClasspath.asPath] }
     }
 
-    private void configureCompileDefaults(final Project project, JavaBasePlugin javaPlugin) {
+    private void configureCompileDefaults() {
         project.tasks.withType(ScalaCompile.class) { ScalaCompile compile ->
-            compile.scalaClasspath = project.configurations[SCALA_TOOLS_CONFIGURATION_NAME]
+            compile.conventionMapping.scalaClasspath = { getScalaClasspath(compile.classpath) }
             compile.conventionMapping.zincClasspath = {
                 def config = project.configurations[ZINC_CONFIGURATION_NAME]
                 if (!compile.scalaCompileOptions.useAnt && config.dependencies.empty) {
                     project.dependencies {
-                        zinc("com.typesafe.zinc:zinc:0.2.0")
+                        zinc("com.typesafe.zinc:zinc:$DEFAULT_ZINC_VERSION")
                     }
                 }
                 config
@@ -111,11 +129,34 @@ class ScalaBasePlugin implements Plugin<Project> {
         }
     }
 
-    private void configureScaladoc(final Project project) {
-        project.getTasks().withType(ScalaDoc.class) {ScalaDoc scalaDoc ->
+    private void configureScaladoc() {
+        project.tasks.withType(ScalaDoc) { ScalaDoc scalaDoc ->
             scalaDoc.conventionMapping.destinationDir = { project.file("$project.docsDir/scaladoc") }
             scalaDoc.conventionMapping.title = { project.extensions.getByType(ReportingExtension).apiDocTitle }
-            scalaDoc.scalaClasspath = project.configurations[SCALA_TOOLS_CONFIGURATION_NAME]
+            scalaDoc.conventionMapping.scalaClasspath = { getScalaClasspath(scalaDoc.classpath) }
+        }
+    }
+
+    private FileCollection getScalaClasspath(Iterable<File> classpath) {
+        def scalaClasspath = project.configurations[SCALA_TOOLS_CONFIGURATION_NAME]
+        if (scalaClasspath.dependencies.empty && !project.repositories.empty) {
+            def scalaVersion = sniffScalaVersion(classpath)
+            if (scalaVersion != null) {
+                scalaClasspath = project.configurations.detachedConfiguration(
+                        new DefaultExternalModuleDependency("org.scala-lang", "scala-compiler", scalaVersion))
+            }
+        }
+        scalaClasspath
+    }
+
+    private String sniffScalaVersion(Iterable<File> classpath) {
+        if (classpath == null) { return null }
+        for (file in classpath) {
+            def matcher = SCALA_LIBRARY_JAR_PATTERN.matcher(file.name)
+            if (matcher.matches()) {
+                return matcher.group(1)
+            }
         }
+        null
     }
 }
\ No newline at end of file
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompile.java b/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompile.java
index c61952d..3e36208 100644
--- a/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompile.java
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompile.java
@@ -20,6 +20,7 @@ import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 
 import org.gradle.api.AntBuilder;
+import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.Project;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.project.IsolatedAntBuilder;
@@ -114,6 +115,7 @@ public class ScalaCompile extends AbstractCompile {
 
     @Override
     protected void compile() {
+        checkScalaClasspathIsNonEmpty();
         DefaultScalaJavaJointCompileSpec spec = new DefaultScalaJavaJointCompileSpec();
         spec.setSource(getSource());
         spec.setDestinationDir(getDestinationDir());
@@ -131,6 +133,12 @@ public class ScalaCompile extends AbstractCompile {
         compiler.execute(spec);
     }
 
+    private void checkScalaClasspathIsNonEmpty() {
+        if (getScalaClasspath().isEmpty()) {
+            throw new InvalidUserDataException("'" + getName() + ".scalaClasspath' must not be empty");
+        }
+    }
+
     private void configureIncrementalCompilation(ScalaCompileSpec spec) {
         Map<File, File> globalAnalysisMap = getOrCreateGlobalAnalysisMap();
         HashMap<File, File> filteredMap = filterForClasspath(globalAnalysisMap, spec.getClasspath());
diff --git a/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaBasePluginTest.groovy b/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaBasePluginTest.groovy
index c6dacfe..ead9945 100644
--- a/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaBasePluginTest.groovy
+++ b/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaBasePluginTest.groovy
@@ -35,25 +35,45 @@ import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 
 public class ScalaBasePluginTest {
-
     private final Project project = HelperUtil.createRootProject()
-    private final ScalaBasePlugin scalaPlugin = new ScalaBasePlugin()
 
     @Test void appliesTheJavaPluginToTheProject() {
-        scalaPlugin.apply(project)
+        project.plugins.apply(ScalaBasePlugin)
         assertTrue(project.getPlugins().hasPlugin(JavaBasePlugin))
     }
 
     @Test void addsScalaToolsConfigurationToTheProject() {
-        scalaPlugin.apply(project)
+        project.plugins.apply(ScalaBasePlugin)
         def configuration = project.configurations.getByName(ScalaBasePlugin.SCALA_TOOLS_CONFIGURATION_NAME)
         assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(toSet()))
         assertFalse(configuration.visible)
         assertTrue(configuration.transitive)
     }
 
+    @Test void defaultsScalaClasspathToScalaToolsConfigurationIfTheLatterIsNonEmpty() {
+        project.plugins.apply(ScalaBasePlugin)
+        project.sourceSets.add('custom')
+        def configuration = project.configurations.scalaTools
+        project.dependencies {
+            scalaTools "org.scala-lang:scala-compiler:2.10"
+        }
+
+        def compileTask = project.tasks.compileCustomScala
+        assertSame(configuration, compileTask.scalaClasspath)
+
+        def consoleTask = project.tasks.scalaCustomConsole
+        assertSame(configuration, consoleTask.classpath)
+
+        def scaladocTask = project.task("scaladoc", type: ScalaDoc)
+        assertSame(configuration, scaladocTask.scalaClasspath)
+    }
+
+    // see ScalaBasePluginIntegrationTest
+    @Test void defaultsScalaClasspathToInferredScalaCompilerDependencyIfScalaToolsConfigurationIsEmpty() {
+    }
+
     @Test void addsZincConfigurationToTheProject() {
-        scalaPlugin.apply(project)
+        project.plugins.apply(ScalaBasePlugin)
         def configuration = project.configurations.getByName(ScalaBasePlugin.ZINC_CONFIGURATION_NAME)
         assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(toSet()))
         assertFalse(configuration.visible)
@@ -61,7 +81,7 @@ public class ScalaBasePluginTest {
     }
 
     @Test void preconfiguresZincClasspathForCompileTasksThatUseZinc() {
-        scalaPlugin.apply(project)
+        project.plugins.apply(ScalaBasePlugin)
         project.sourceSets.add('custom')
         def task = project.tasks.compileCustomScala
         task.scalaCompileOptions.useAnt = false
@@ -70,7 +90,7 @@ public class ScalaBasePluginTest {
     }
 
     @Test void doesNotPreconfigureZincClasspathForCompileTasksThatUseAnt() {
-        scalaPlugin.apply(project)
+        project.plugins.apply(ScalaBasePlugin)
         project.sourceSets.add('custom')
         def task = project.tasks.compileCustomScala
         task.scalaCompileOptions.useAnt = true
@@ -79,7 +99,7 @@ public class ScalaBasePluginTest {
     }
 
     @Test void addsScalaConventionToNewSourceSet() {
-        scalaPlugin.apply(project)
+        project.plugins.apply(ScalaBasePlugin)
 
         def sourceSet = project.sourceSets.add('custom')
         assertThat(sourceSet.scala.displayName, equalTo("custom Scala source"))
@@ -87,7 +107,7 @@ public class ScalaBasePluginTest {
     }
 
     @Test void addsCompileTaskForNewSourceSet() {
-        scalaPlugin.apply(project)
+        project.plugins.apply(ScalaBasePlugin)
 
         project.sourceSets.add('custom')
         def task = project.tasks['compileCustomScala']
@@ -100,7 +120,7 @@ public class ScalaBasePluginTest {
     }
 
     @Test void preconfiguresIncrementalCompileOptions() {
-        scalaPlugin.apply(project)
+        project.plugins.apply(ScalaBasePlugin)
 
         project.sourceSets.add('custom')
         project.tasks.add('customJar', Jar)
@@ -112,7 +132,7 @@ public class ScalaBasePluginTest {
     }
 
     @Test void incrementalCompileOptionsCanBeOverridden() {
-        scalaPlugin.apply(project)
+        project.plugins.apply(ScalaBasePlugin)
 
         project.sourceSets.add('custom')
         project.tasks.add('customJar', Jar)
@@ -126,7 +146,7 @@ public class ScalaBasePluginTest {
     }
     
     @Test void dependenciesOfJavaPluginTasksIncludeScalaCompileTasks() {
-        scalaPlugin.apply(project)
+        project.plugins.apply(ScalaBasePlugin)
 
         project.sourceSets.add('custom')
         def task = project.tasks['customClasses']
@@ -134,7 +154,7 @@ public class ScalaBasePluginTest {
     }
 
     @Test void configuresCompileTasksDefinedByTheBuildScript() {
-        scalaPlugin.apply(project)
+        project.plugins.apply(ScalaBasePlugin)
 
         def task = project.task('otherCompile', type: ScalaCompile)
         assertThat(task.source, isEmpty())
@@ -143,7 +163,7 @@ public class ScalaBasePluginTest {
     }
 
     @Test void configuresScalaDocTasksDefinedByTheBuildScript() {
-        scalaPlugin.apply(project)
+        project.plugins.apply(ScalaBasePlugin)
 
         def task = project.task('otherScaladoc', type: ScalaDoc)
         assertThat(task.destinationDir, equalTo(project.file("$project.docsDir/scaladoc")))
diff --git a/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaPluginTest.groovy b/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaPluginTest.groovy
index 659b634..8c21a59 100644
--- a/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaPluginTest.groovy
+++ b/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaPluginTest.groovy
@@ -23,23 +23,23 @@ import org.gradle.api.tasks.scala.ScalaDoc
 import org.gradle.util.HelperUtil
 import org.gradle.util.Matchers
 import org.junit.Test
+
 import static org.gradle.util.Matchers.dependsOn
 import static org.gradle.util.WrapUtil.toLinkedSet
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertThat
 import static org.junit.Assert.assertTrue
 
-public class ScalaPluginTest {
-
+class ScalaPluginTest {
     private final Project project = HelperUtil.createRootProject()
     private final ScalaPlugin scalaPlugin = new ScalaPlugin()
 
-    @Test public void appliesTheJavaPluginToTheProject() {
+    @Test void appliesTheJavaPluginToTheProject() {
         scalaPlugin.apply(project)
         assertTrue(project.getPlugins().hasPlugin(JavaPlugin))
     }
 
-    @Test public void addsScalaConventionToEachSourceSetAndAppliesMappings() {
+    @Test void addsScalaConventionToEachSourceSetAndAppliesMappings() {
         scalaPlugin.apply(project)
 
         def sourceSet = project.sourceSets.main
@@ -51,7 +51,7 @@ public class ScalaPluginTest {
         assertThat(sourceSet.scala.srcDirs, equalTo(toLinkedSet(project.file("src/test/scala"))))
     }
 
-    @Test public void addsCompileTaskForEachSourceSet() {
+    @Test void addsCompileTaskForEachSourceSet() {
         scalaPlugin.apply(project)
 
         def task = project.tasks['compileScala']
@@ -79,7 +79,7 @@ public class ScalaPluginTest {
         assertThat(task, dependsOn(hasItem('compileTestScala')))
     }
     
-    @Test public void addsScalaDocTasksToTheProject() {
+    @Test void addsScalaDocTasksToTheProject() {
         scalaPlugin.apply(project)
 
         def task = project.tasks[ScalaPlugin.SCALA_DOC_TASK_NAME]
@@ -91,7 +91,7 @@ public class ScalaPluginTest {
         assertThat(task.title, equalTo(project.extensions.getByType(ReportingExtension).apiDocTitle))
     }
 
-    @Test public void configuresScalaDocTasksDefinedByTheBuildScript() {
+    @Test void configuresScalaDocTasksDefinedByTheBuildScript() {
         scalaPlugin.apply(project)
 
         def task = project.task('otherScaladoc', type: ScalaDoc)
diff --git a/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaCompileTest.java b/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaCompileTest.java
index af416aa..be14ffd 100644
--- a/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaCompileTest.java
+++ b/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaCompileTest.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.api.tasks.scala;
 
+import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.ConventionTask;
 import org.gradle.api.internal.tasks.compile.Compiler;
@@ -37,6 +38,7 @@ public class ScalaCompileTest extends AbstractCompileTest {
 
     private Compiler<ScalaJavaJointCompileSpec> scalaCompiler;
     private JUnit4Mockery context = new JUnit4GroovyMockery();
+    private FileCollection scalaClasspath;
 
     @Override
     public AbstractCompile getCompile() {
@@ -62,7 +64,18 @@ public class ScalaCompileTest extends AbstractCompileTest {
     public void testExecuteDoingWork() {
         setUpMocksAndAttributes(scalaCompile);
         context.checking(new Expectations() {{
-            one(scalaCompiler).execute(with(IsNull.<ScalaJavaJointCompileSpec>notNullValue()));
+            allowing(scalaClasspath).isEmpty(); will(returnValue(false));
+            one(scalaCompiler).execute((ScalaJavaJointCompileSpec) with(IsNull.notNullValue()));
+        }});
+
+        scalaCompile.compile();
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void testMoansIfScalaClasspathIsEmpty() {
+        setUpMocksAndAttributes(scalaCompile);
+        context.checking(new Expectations() {{
+            allowing(scalaClasspath).isEmpty(); will(returnValue(true));
         }});
 
         scalaCompile.compile();
@@ -75,15 +88,9 @@ public class ScalaCompileTest extends AbstractCompileTest {
         compile.setSourceCompatibility("1.5");
         compile.setTargetCompatibility("1.5");
         compile.setDestinationDir(destDir);
-        compile.setScalaClasspath(context.mock(FileCollection.class));
-
-        final FileCollection configuration = context.mock(FileCollection.class);
-        context.checking(new Expectations(){{
-            allowing(configuration).iterator();
-            will(returnIterator(TEST_DEPENDENCY_MANAGER_CLASSPATH));
-        }});
-
-        compile.setClasspath(configuration);
+        scalaClasspath = context.mock(FileCollection.class);
+        compile.setScalaClasspath(scalaClasspath);
+        compile.setClasspath(context.mock(FileCollection.class));
     }
 
 }
diff --git a/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningSamplesSpec.groovy b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningSamplesSpec.groovy
index 24255fa..88cabf8 100644
--- a/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningSamplesSpec.groovy
+++ b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningSamplesSpec.groovy
@@ -17,9 +17,9 @@
 package org.gradle.plugins.signing
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.MavenRepository
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.integtests.fixtures.UsesSample
+import org.gradle.test.fixtures.maven.MavenRepository
 import org.junit.Rule
 
 class SigningSamplesSpec extends AbstractIntegrationSpec {
diff --git a/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SigningProjectSpec.groovy b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SigningProjectSpec.groovy
index a2babf2..5068fdf 100644
--- a/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SigningProjectSpec.groovy
+++ b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SigningProjectSpec.groovy
@@ -15,18 +15,18 @@
  */
 package org.gradle.plugins.signing
 
-import spock.lang.*
 import org.gradle.api.Project
-import org.gradle.api.tasks.bundling.*
+import org.gradle.api.tasks.bundling.Jar
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.HelperUtil
-import org.gradle.util.TemporaryFolder
 import org.junit.Rule
+import spock.lang.Specification
 
 class SigningProjectSpec extends Specification {
     
     static final DEFAULT_KEY_SET = "gradle"
     
-    @Rule public TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
         
     Project project = HelperUtil.createRootProject()
     
diff --git a/subprojects/sonar/src/integTest/groovy/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest.groovy b/subprojects/sonar/src/integTest/groovy/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest.groovy
index 7d53643..aaf6a06 100644
--- a/subprojects/sonar/src/integTest/groovy/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest.groovy
+++ b/subprojects/sonar/src/integTest/groovy/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest.groovy
@@ -17,17 +17,15 @@
 package org.gradle.api.plugins.sonar
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.mortbay.jetty.Server
-import org.mortbay.jetty.webapp.WebAppContext
 import org.gradle.integtests.fixtures.TestResources
-
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.AvailablePortFinder
+import org.gradle.util.ClasspathUtil
 import org.junit.Rule
-import spock.lang.Shared
+import org.mortbay.jetty.Server
+import org.mortbay.jetty.webapp.WebAppContext
 import spock.lang.AutoCleanup
-import org.gradle.util.Resources
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.ClasspathUtil
-import org.gradle.util.AvailablePortFinder
+import spock.lang.Shared
 
 class SonarSmokeIntegrationTest extends AbstractIntegrationSpec {
     @Shared
@@ -37,7 +35,7 @@ class SonarSmokeIntegrationTest extends AbstractIntegrationSpec {
     Server webServer = new Server(0)
 
     @Rule
-    TemporaryFolder tempDir = new TemporaryFolder()
+    TestNameTestDirectoryProvider tempDir = new TestNameTestDirectoryProvider()
 
     @Rule
     TestResources testResources
@@ -70,14 +68,14 @@ sonar.embeddedDatabase.port=$databasePort
     }
 
     def "can run Sonar analysis"() {
-        distribution.requireIsolatedDaemons()
+        executer.requireIsolatedDaemons()
         // Without forking, we run into problems with Sonar's BootStrapClassLoader, at least when running from IDEA.
         // Problem is that BootStrapClassLoader, although generally isolated from its parent(s), always
         // delegates to the system class loader. That class loader holds the test class path and therefore
         // also the Sonar dependencies with "provided" scope. Hence, the Sonar dependencies get loaded by
         // the wrong class loader.
         when:
-        executer.withForkingExecuter()
+        executer.requireGradleHome(true)
                 .withArgument("-PserverUrl=http://localhost:${webServer.connectors[0].localPort}")
                 .withArgument("-PdatabaseUrl=jdbc:h2:tcp://localhost:$databasePort/mem:sonartest")
                 .withTasks("sonarAnalyze").run()
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/SonarAnalyze.groovy b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/SonarAnalyze.groovy
index e1a0e20..0f7a50b 100644
--- a/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/SonarAnalyze.groovy
+++ b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/SonarAnalyze.groovy
@@ -21,6 +21,7 @@ import org.gradle.api.tasks.TaskAction
 import org.gradle.util.ClasspathUtil
 
 import org.gradle.api.plugins.sonar.model.SonarRootModel
+import org.gradle.util.GFileUtils
 
 /**
  * Analyzes a project hierarchy and writes the results to the
@@ -34,7 +35,7 @@ class SonarAnalyze extends ConventionTask {
 
     @TaskAction
     void analyze() {
-        rootModel.bootstrapDir.mkdirs()
+        GFileUtils.mkdirs(rootModel.bootstrapDir)
         def bootstrapper = new Bootstrapper("Gradle", rootModel.server.url, rootModel.bootstrapDir)
 
         def classLoader = bootstrapper.createClassLoader(
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/AutoTestedSamplesToolingApiTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/AutoTestedSamplesToolingApiTest.groovy
index 609cee0..7ea9830 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/AutoTestedSamplesToolingApiTest.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/AutoTestedSamplesToolingApiTest.groovy
@@ -18,9 +18,9 @@ package org.gradle.integtests.tooling
 
 import org.gradle.api.JavaVersion
 import org.gradle.integtests.fixtures.AutoTestedSamplesUtil
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.tooling.model.Element
 import org.gradle.util.ClasspathUtil
-import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.IgnoreIf
 import spock.lang.Specification
@@ -31,7 +31,7 @@ import spock.lang.Specification
 @IgnoreIf({!JavaVersion.current().java6Compatible})
 public class AutoTestedSamplesToolingApiTest extends Specification {
 
-    @Rule public final TemporaryFolder temp = new TemporaryFolder()
+    @Rule public final TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
 
     void runSamples() {
         expect:
@@ -65,14 +65,14 @@ public class Sample {
     void tryCompile(String source) {
         //TODO SF generalize and move the test out of integ tests, add unit tests
         source = normalize(source)
-        def sourceFile = temp.dir.file("Sample.java")
+        def sourceFile = temp.testDirectory.file("Sample.java")
         sourceFile.text = source
 
         def compiler = ("javax.tools.ToolProvider" as Class).getSystemJavaCompiler()
         def fileManager = compiler.getStandardFileManager(null, null, null);
 
         def location = ("javax.tools.StandardLocation" as Class).CLASS_OUTPUT
-        fileManager.setLocation(location, [temp.dir]);
+        fileManager.setLocation(location, [temp.testDirectory]);
 
         location = ("javax.tools.StandardLocation" as Class).CLASS_PATH
         fileManager.setLocation(location, [ClasspathUtil.getClasspathForClass(Element)]);
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ConcurrentToolingApiIntegrationSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ConcurrentToolingApiIntegrationSpec.groovy
index 9c798b2..fd3500b 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ConcurrentToolingApiIntegrationSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ConcurrentToolingApiIntegrationSpec.groovy
@@ -16,13 +16,17 @@
 
 package org.gradle.integtests.tooling
 
-import org.gradle.integtests.fixtures.BasicGradleDistribution
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.ReleasedVersions
+import org.gradle.integtests.fixtures.executer.GradleDistribution
+import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext
+import org.gradle.integtests.fixtures.executer.UnderDevelopmentGradleDistribution
+import org.gradle.integtests.fixtures.versions.ReleasedVersionDistributions
 import org.gradle.integtests.tooling.fixture.ConfigurableOperation
 import org.gradle.integtests.tooling.fixture.ToolingApi
+import org.gradle.internal.classpath.ClassPath
 import org.gradle.logging.ProgressLoggerFactory
-import org.gradle.tests.fixtures.ConcurrentTestUtil
+import org.gradle.test.fixtures.ConcurrentTestUtil
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.tooling.GradleConnector
 import org.gradle.tooling.ProjectConnection
 import org.gradle.tooling.internal.consumer.ConnectorServices
@@ -32,15 +36,16 @@ import org.gradle.tooling.model.idea.IdeaProject
 import org.junit.Rule
 import spock.lang.Issue
 import spock.lang.Specification
-import org.gradle.internal.classpath.ClassPath
+
 import java.util.concurrent.CopyOnWriteArrayList
 
 @Issue("GRADLE-1933")
 class ConcurrentToolingApiIntegrationSpec extends Specification {
 
     @Rule final ConcurrentTestUtil concurrent = new ConcurrentTestUtil()
-    @Rule final GradleDistribution dist = new GradleDistribution()
-    final ToolingApi toolingApi = new ToolingApi(dist)
+    @Rule final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
+    final GradleDistribution dist = new UnderDevelopmentGradleDistribution()
+    final ToolingApi toolingApi = new ToolingApi(dist, temporaryFolder)
 
     int threads = 3
 
@@ -56,25 +61,29 @@ class ConcurrentToolingApiIntegrationSpec extends Specification {
     }
 
     def "handles the same target gradle version concurrently"() {
-        dist.file('build.gradle')  << "apply plugin: 'java'"
+        file('build.gradle')  << "apply plugin: 'java'"
 
         when:
         threads.times {
-            concurrent.start { useToolingApi(new GradleDistribution()) }
+            concurrent.start { useToolingApi(new UnderDevelopmentGradleDistribution()) }
         }
 
         then:
         concurrent.finished()
     }
 
+    TestFile file(Object... s) {
+        temporaryFolder.file(s)
+    }
+
     def "handles different target gradle versions concurrently"() {
         given:
         def current = dist
-        def last = new ReleasedVersions(dist).getLast()
+        def last = new ReleasedVersionDistributions().getMostRecentFinalRelease() 
         assert current != last
         println "Combination of versions used: current - $current, last - $last"
 
-        dist.file('build.gradle')  << "apply plugin: 'java'"
+        file('build.gradle')  << "apply plugin: 'java'"
 
         when:
         concurrent.start { useToolingApi(current) }
@@ -84,8 +93,8 @@ class ConcurrentToolingApiIntegrationSpec extends Specification {
         concurrent.finished()
     }
 
-    def useToolingApi(BasicGradleDistribution target) {
-        new ToolingApi(target, dist.userHomeDir, { dist.testDir }, false).withConnection { ProjectConnection connection ->
+    def useToolingApi(GradleDistribution target) {
+        new ToolingApi(target, new IntegrationTestBuildContext().gradleUserHomeDir, temporaryFolder, false).withConnection { ProjectConnection connection ->
             try {
                 def model = connection.getModel(IdeaProject)
                 assert model != null
@@ -100,7 +109,7 @@ See the full stacktrace and the list of causes to investigate""", e);
 
     def "can share connection when running build"() {
         given:
-        dist.file("build.gradle") << """
+        file("build.gradle") << """
 def text = System.in.text
 System.out.println 'out=' + text
 System.err.println 'err=' + text
@@ -133,7 +142,7 @@ project.description = text
     def "handles standard input concurrently when getting model"() {
         when:
         threads.times { idx ->
-            dist.file("build$idx/build.gradle") << "description = System.in.text"
+            file("build$idx/build.gradle") << "description = System.in.text"
         }
 
         then:
@@ -154,7 +163,7 @@ project.description = text
     def "handles standard input concurrently when running build"() {
         when:
         threads.times { idx ->
-            dist.file("build$idx/build.gradle") << "task show << { println System.in.text}"
+            file("build$idx/build.gradle") << "task show << { println System.in.text}"
         }
 
         then:
@@ -175,8 +184,8 @@ project.description = text
 
     def "during task execution receives distribution progress including waiting for the other thread"() {
         given:
-        dist.file("build1/build.gradle") << "task foo1"
-        dist.file("build2/build.gradle") << "task foo2"
+        file("build1/build.gradle") << "task foo1"
+        file("build2/build.gradle") << "task foo2"
 
         when:
         def allProgress = new CopyOnWriteArrayList<String>()
@@ -184,7 +193,7 @@ project.description = text
         concurrent.start {
             def connector = toolingApi.connector()
             distributionOperation(connector, { it.description = "download for 1"; Thread.sleep(500) } )
-            connector.forProjectDirectory(dist.file("build1"))
+            connector.forProjectDirectory(file("build1"))
 
             toolingApi.withConnection(connector) { connection ->
                 def build = connection.newBuild()
@@ -199,7 +208,7 @@ project.description = text
         concurrent.start {
             def connector = toolingApi.connector()
             distributionOperation(connector, { it.description = "download for 2"; Thread.sleep(500) } )
-            connector.forProjectDirectory(dist.file("build2"))
+            connector.forProjectDirectory(file("build2"))
 
             def connection = connector.connect()
 
@@ -226,7 +235,7 @@ project.description = text
     def "during model building receives distribution progress"() {
         given:
         threads.times { idx ->
-            dist.file("build$idx/build.gradle") << "apply plugin: 'java'"
+            file("build$idx/build.gradle") << "apply plugin: 'java'"
         }
 
         when:
@@ -283,7 +292,7 @@ project.description = text
         when:
         //create build folders with slightly different builds
         threads.times { idx ->
-            dist.file("build$idx/build.gradle") << """
+            file("build$idx/build.gradle") << """
 System.out.println 'this is stdout: $idx'
 System.err.println 'this is stderr: $idx'
 logger.lifecycle 'this is lifecycle: $idx'
@@ -318,7 +327,7 @@ logger.lifecycle 'this is lifecycle: $idx'
         when:
         //create build folders with slightly different builds
         threads.times { idx ->
-            dist.file("build$idx/build.gradle") << """
+            file("build$idx/build.gradle") << """
 System.out.println 'this is stdout: $idx'
 System.err.println 'this is stderr: $idx'
 logger.lifecycle 'this is lifecycle: $idx'
@@ -351,7 +360,7 @@ logger.lifecycle 'this is lifecycle: $idx'
 
     def withConnectionInDir(String dir, Closure cl) {
         GradleConnector connector = toolingApi.connector()
-        connector.forProjectDirectory(dist.file(dir))
+        connector.forProjectDirectory(file(dir))
         toolingApi.withConnection(connector, cl)
     }
 }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/GlobalLoggingManipulationIntegrationTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/GlobalLoggingManipulationIntegrationTest.groovy
index c8b5610..0101a52 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/GlobalLoggingManipulationIntegrationTest.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/GlobalLoggingManipulationIntegrationTest.groovy
@@ -15,16 +15,17 @@
  */
 package org.gradle.integtests.tooling
 
-import java.util.logging.LogManager
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.tooling.fixture.ToolingApi
 import org.gradle.tooling.model.GradleProject
 import org.gradle.util.RedirectStdIn
 import org.junit.Rule
 
+import java.util.logging.LogManager
+
 class GlobalLoggingManipulationIntegrationTest extends AbstractIntegrationSpec {
     @Rule RedirectStdIn stdIn
-    final ToolingApi toolingApi = new ToolingApi(distribution)
+    final ToolingApi toolingApi = new ToolingApi(distribution, temporaryFolder)
 
     def "tooling api does not replace standard streams"() {
         //(SF) only checking if the instances of out and err were not replaced
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/SamplesToolingApiIntegrationTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/SamplesToolingApiIntegrationTest.groovy
index a4790c3..5701102 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/SamplesToolingApiIntegrationTest.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/SamplesToolingApiIntegrationTest.groovy
@@ -15,15 +15,22 @@
  */
 package org.gradle.integtests.tooling
 
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.IntegrationTestHint
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.integtests.fixtures.UsesSample
+import org.gradle.integtests.fixtures.executer.ExecutionResult
+import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
+import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext
 import org.gradle.util.TextUtil
 import org.junit.Rule
-import spock.lang.Specification
-import org.gradle.integtests.fixtures.*
 
-class SamplesToolingApiIntegrationTest extends Specification {
-    @Rule public final GradleDistribution distribution = new GradleDistribution()
+class SamplesToolingApiIntegrationTest extends AbstractIntegrationSpec {
+
     @Rule public final Sample sample = new Sample()
 
+    private IntegrationTestBuildContext buildContext
+
     @UsesSample('toolingApi/eclipse')
     def "can use tooling API to build Eclipse model"() {
         tweakProject()
@@ -78,12 +85,13 @@ class SamplesToolingApiIntegrationTest extends Specification {
         def buildScript = buildFile.text
         def index = buildScript.indexOf('repositories {')
         assert index >= 0
+        buildContext = new IntegrationTestBuildContext()
         buildScript = buildScript.substring(0, index) + """
 repositories {
-    maven { url "${distribution.libsRepo.toURI()}" }
+    maven { url "${buildContext.libsRepo.toURI()}" }
 }
 run {
-    args = ["${TextUtil.escapeString(distribution.gradleHomeDir.absolutePath)}", "${TextUtil.escapeString(distribution.userHomeDir.absolutePath)}"]
+    args = ["${TextUtil.escapeString(buildContext.gradleHomeDir.absolutePath)}", "${TextUtil.escapeString(executer.gradleUserHomeDir.absolutePath)}"]
     systemProperty 'org.gradle.daemon.idletimeout', 10000
     systemProperty 'org.gradle.daemon.registry.base', "${TextUtil.escapeString(projectDir.file("daemon").absolutePath)}"
 }
@@ -97,7 +105,8 @@ run {
 
     private ExecutionResult run() {
         try {
-            return new GradleDistributionExecuter(distribution).inDirectory(sample.dir)
+            return new GradleContextualExecuter(distribution, temporaryFolder)
+                    .inDirectory(sample.dir)
                     .withTasks('run')
                     .withDaemonIdleTimeoutSecs(60)
                     .run()
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiClasspathIntegrationTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiClasspathIntegrationTest.groovy
new file mode 100644
index 0000000..8e2ae48
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiClasspathIntegrationTest.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.tooling
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.tooling.fixture.ToolingApiDistribution
+import org.gradle.integtests.tooling.fixture.ToolingApiDistributionResolver
+
+class ToolingApiClasspathIntegrationTest extends AbstractIntegrationSpec {
+
+    def "tooling api classpath contains only tooling-api jar and slf4j"() {
+        when:
+        ToolingApiDistributionResolver resolver = new ToolingApiDistributionResolver().withDefaultRepository().withExternalToolingApiDistribution()
+        ToolingApiDistribution resolve = resolver.resolve(distribution.getVersion().version)
+
+        then:
+        resolve.classpath.files.size() == 2
+        resolve.classpath.files.any {it.name ==~ /slf4j-api-.*\.jar/}
+        resolve.classpath.files.find {it.name ==~ /gradle-tooling-api.*\.jar/}.size() < 1.5 * 1024 * 1024
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiIntegrationTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiIntegrationTest.groovy
index b1825be..d3fba5a 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiIntegrationTest.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiIntegrationTest.groovy
@@ -15,25 +15,31 @@
  */
 package org.gradle.integtests.tooling
 
-import org.gradle.integtests.fixtures.BasicGradleDistribution
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.ReleasedVersions
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.executer.GradleDistribution
+import org.gradle.integtests.fixtures.executer.GradleHandle
+import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext
+import org.gradle.integtests.fixtures.versions.ReleasedVersionDistributions
+import org.gradle.integtests.tooling.fixture.TextUtil
 import org.gradle.integtests.tooling.fixture.ToolingApi
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.tooling.GradleConnector
 import org.gradle.tooling.UnsupportedVersionException
 import org.gradle.tooling.model.GradleProject
 import org.gradle.util.GradleVersion
-import org.gradle.util.TestFile
-import org.junit.Rule
-import spock.lang.Specification
 import spock.lang.Issue
-import org.gradle.integtests.tooling.fixture.TextUtil
-import org.gradle.integtests.fixtures.GradleHandle
 
-class ToolingApiIntegrationTest extends Specification {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    final ToolingApi toolingApi = new ToolingApi(dist)
-    final BasicGradleDistribution otherVersion = new ReleasedVersions(dist).last
-    TestFile projectDir = dist.testDir
+class ToolingApiIntegrationTest extends AbstractIntegrationSpec {
+
+    final ToolingApi toolingApi = new ToolingApi(distribution, temporaryFolder)
+    final GradleDistribution otherVersion = new ReleasedVersionDistributions().mostRecentFinalRelease
+    final IntegrationTestBuildContext buildContext = new IntegrationTestBuildContext()
+
+    TestFile projectDir
+
+    def setup() {
+        projectDir = temporaryFolder.testDirectory
+    }
 
     def "ensure the previous version supports short-lived daemons"() {
         expect:
@@ -55,9 +61,9 @@ class ToolingApiIntegrationTest extends Specification {
 
         projectDir.file('build.gradle').text = """
 task wrapper(type: Wrapper) { distributionUrl = '${otherVersion.binDistribution.toURI()}' }
-task check << { assert gradle.gradleVersion == '${otherVersion.version}' }
+task check << { assert gradle.gradleVersion == '${otherVersion.version.version}' }
 """
-        dist.executer().withTasks('wrapper').run()
+        executer.withTasks('wrapper').run()
 
         when:
         toolingApi.withConnector { connector ->
@@ -76,11 +82,11 @@ task check << { assert gradle.gradleVersion == '${otherVersion.version}' }
         projectDir.file('build.gradle') << """
 task wrapper(type: Wrapper) { distributionUrl = '${otherVersion.binDistribution.toURI()}' }
 allprojects {
-    task check << { assert gradle.gradleVersion == '${otherVersion.version}' }
+    task check << { assert gradle.gradleVersion == '${otherVersion.version.version}' }
 }
 """
         projectDir.file('child').createDir()
-        dist.executer().withTasks('wrapper').run()
+        executer.withTasks('wrapper').run()
 
         when:
         toolingApi.withConnector { connector ->
@@ -96,7 +102,7 @@ allprojects {
 
     def "can specify a gradle installation to use"() {
         toolingApi.isEmbedded = false
-        projectDir.file('build.gradle').text = "assert gradle.gradleVersion == '${otherVersion.version}'"
+        projectDir.file('build.gradle').text = "assert gradle.gradleVersion == '${otherVersion.version.version}'"
 
         when:
         toolingApi.withConnector { connector ->
@@ -110,7 +116,7 @@ allprojects {
 
     def "can specify a gradle distribution to use"() {
         toolingApi.isEmbedded = false
-        projectDir.file('build.gradle').text = "assert gradle.gradleVersion == '${otherVersion.version}'"
+        projectDir.file('build.gradle').text = "assert gradle.gradleVersion == '${otherVersion.version.version}'"
 
         when:
         toolingApi.withConnector { connector ->
@@ -124,11 +130,11 @@ allprojects {
 
     def "can specify a gradle version to use"() {
         toolingApi.isEmbedded = false
-        projectDir.file('build.gradle').text = "assert gradle.gradleVersion == '${otherVersion.version}'"
+        projectDir.file('build.gradle').text = "assert gradle.gradleVersion == '${otherVersion.version.version}'"
 
         when:
-        toolingApi.withConnector { connector ->
-            connector.useGradleVersion(otherVersion.version)
+        toolingApi.withConnector { GradleConnector connector ->
+            connector.useGradleVersion(otherVersion.version.version)
         }
         GradleProject model = toolingApi.withConnection { connection -> connection.getModel(GradleProject.class) }
 
@@ -137,15 +143,15 @@ allprojects {
     }
 
     def "tooling api reports an error when the specified gradle version does not support the tooling api"() {
-        def dist = dist.previousVersion('0.9.2').binDistribution
+        def distroZip = buildContext.distribution('0.9.2').binDistribution
 
         when:
-        toolingApi.withConnector { connector -> connector.useDistribution(dist.toURI()) }
+        toolingApi.withConnector { connector -> connector.useDistribution(distroZip.toURI()) }
         toolingApi.maybeFailWithConnection { connection -> connection.getModel(GradleProject.class) }
 
         then:
         UnsupportedVersionException e = thrown()
-        e.message == "The specified Gradle distribution '${dist.toURI()}' is not supported by this tooling API version (${GradleVersion.current().version}, protocol version 4)"
+        e.message == "The specified Gradle distribution '${distroZip.toURI()}' is not supported by this tooling API version (${GradleVersion.current().version}, protocol version 4)"
     }
 
     @Issue("GRADLE-2419")
@@ -157,24 +163,26 @@ allprojects {
         def stopTimeoutMs = 10000
         def retryIntervalMs = 500
 
+        def path = executer.gradleUserHomeDir.absolutePath
+        def path1 = distribution.gradleHomeDir.absolutePath
         buildFile << """
             apply plugin: 'java'
             apply plugin: 'application'
 
             repositories {
-                maven { url "${dist.libsRepo.toURI()}" }
+                maven { url "${new IntegrationTestBuildContext().libsRepo.toURI()}" }
                 maven { url "http://repo.gradle.org/gradle/repo" }
             }
 
             dependencies {
-                compile "org.gradle:gradle-tooling-api:${dist.version}"
-                runtime 'org.slf4j:slf4j-simple:1.6.6'
+                compile "org.gradle:gradle-tooling-api:${distribution.version.version}"
+                runtime 'org.slf4j:slf4j-simple:1.7.2'
             }
 
             mainClassName = 'Main'
 
             run {
-                args = ["${TextUtil.escapeString(dist.gradleHomeDir.absolutePath)}", "${TextUtil.escapeString(dist.userHomeDir.absolutePath)}"]
+                args = ["${TextUtil.escapeString(path1)}", "${TextUtil.escapeString(path)}"]
                 systemProperty 'org.gradle.daemon.idletimeout', 10000
                 systemProperty 'org.gradle.daemon.registry.base', "${TextUtil.escapeString(projectDir.file("daemon").absolutePath)}"
             }
@@ -242,8 +250,7 @@ allprojects {
         """
 
         when:
-        GradleHandle handle = dist.executer()
-                .inDirectory(projectDir)
+        GradleHandle handle = executer.inDirectory(projectDir)
                 .withTasks('run')
                 .withDaemonIdleTimeoutSecs(60)
                 .start()
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiRemoteIntegrationTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiRemoteIntegrationTest.groovy
index 8036678..6f4a779 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiRemoteIntegrationTest.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiRemoteIntegrationTest.groovy
@@ -29,7 +29,7 @@ import static org.junit.Assert.assertThat
 
 class ToolingApiRemoteIntegrationTest extends AbstractIntegrationSpec {
     @Rule HttpServer server = new HttpServer()
-    final ToolingApi toolingApi = new ToolingApi(distribution, distribution.userHomeDir, { getTestDir() }, false)
+    final ToolingApi toolingApi = new ToolingApi(distribution, executer.gradleUserHomeDir, temporaryFolder, false)
 
     void setup() {
         server.start()
@@ -38,7 +38,7 @@ class ToolingApiRemoteIntegrationTest extends AbstractIntegrationSpec {
     }
 
     public void "downloads distribution with valid useragent information"() {
-        assert distribution.binDistribution.exists(): "bin distribution must exist to run this test, you need to run the :binZip task"
+        assert distribution.binDistribution.exists() : "bin distribution must exist to run this test, you need to run the :binZip task"
         expect:
         server.allowGetOrHead("/dist", distribution.binDistribution)
         server.expectUserAgent(matchesNameAndVersion("Gradle Tooling API", GradleVersion.current().getVersion()))
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApi.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApi.groovy
index c79b0d9..24656e2 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApi.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApi.groovy
@@ -15,10 +15,11 @@
  */
 package org.gradle.integtests.tooling.fixture
 
-import org.gradle.integtests.fixtures.BasicGradleDistribution
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
 import org.gradle.integtests.fixtures.IntegrationTestHint
+import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
+import org.gradle.integtests.fixtures.executer.GradleDistribution
+import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext
+import org.gradle.test.fixtures.file.TestDirectoryProvider
 import org.gradle.tooling.GradleConnector
 import org.gradle.tooling.ProjectConnection
 import org.gradle.tooling.UnsupportedVersionException
@@ -30,22 +31,22 @@ import java.util.concurrent.TimeUnit
 class ToolingApi {
     private static final Logger LOGGER = LoggerFactory.getLogger(ToolingApi)
 
-    private BasicGradleDistribution dist
-    private Closure getProjectDir
+    private GradleDistribution dist
+    private TestDirectoryProvider testWorkDirProvider
     private File userHomeDir
 
     private final List<Closure> connectorConfigurers = []
     boolean isEmbedded
     boolean verboseLogging = LOGGER.debugEnabled
 
-    ToolingApi(GradleDistribution dist) {
-        this(dist, dist.userHomeDir, { dist.testDir }, GradleDistributionExecuter.systemPropertyExecuter == GradleDistributionExecuter.Executer.embedded)
+    ToolingApi(GradleDistribution dist, TestDirectoryProvider testWorkDirProvider) {
+        this(dist, new IntegrationTestBuildContext().gradleUserHomeDir, testWorkDirProvider, GradleContextualExecuter.embedded)
     }
 
-    ToolingApi(BasicGradleDistribution dist, File userHomeDir, Closure getProjectDir, boolean isEmbedded) {
+    ToolingApi(GradleDistribution dist, File userHomeDir, TestDirectoryProvider testWorkDirProvider, boolean isEmbedded) {
         this.dist = dist
         this.userHomeDir = userHomeDir
-        this.getProjectDir = getProjectDir
+        this.testWorkDirProvider = testWorkDirProvider
         this.isEmbedded = isEmbedded
     }
 
@@ -87,7 +88,7 @@ class ToolingApi {
     GradleConnector connector() {
         GradleConnector connector = GradleConnector.newConnector()
         connector.useGradleUserHomeDir(userHomeDir)
-        connector.forProjectDirectory(getProjectDir().absoluteFile)
+        connector.forProjectDirectory(testWorkDirProvider.testDirectory)
         connector.searchUpwards(false)
         connector.daemonMaxIdleTime(60, TimeUnit.SECONDS)
         if (connector.metaClass.hasProperty(connector, 'verboseLogging')) {
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiCompatibilitySuiteRunner.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiCompatibilitySuiteRunner.groovy
index 54d8034..2cd1116 100755
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiCompatibilitySuiteRunner.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiCompatibilitySuiteRunner.groovy
@@ -17,7 +17,7 @@ package org.gradle.integtests.tooling.fixture
 
 import org.gradle.integtests.fixtures.AbstractCompatibilityTestRunner
 import org.gradle.integtests.fixtures.AbstractMultiTestRunner
-import org.gradle.integtests.fixtures.BasicGradleDistribution
+import org.gradle.integtests.fixtures.executer.GradleDistribution
 import org.gradle.internal.os.OperatingSystem
 import org.gradle.util.*
 
@@ -47,34 +47,34 @@ class ToolingApiCompatibilitySuiteRunner extends AbstractCompatibilityTestRunner
     protected void createExecutions() {
         ToolingApiDistributionResolver resolver = new ToolingApiDistributionResolver().withDefaultRepository()
 
-        add(new Permutation(resolver.resolve(current.version), current))
+        add(new Permutation(resolver.resolve(current.version.version), current))
         previous.each {
             if (it.toolingApiSupported) {
-                add(new Permutation(resolver.resolve(current.version), it))
-                add(new Permutation(resolver.resolve(it.version), current))
+                add(new Permutation(resolver.resolve(current.version.version), it))
+                add(new Permutation(resolver.resolve(it.version.version), current))
             }
         }
     }
 
     private class Permutation extends AbstractMultiTestRunner.Execution {
         final ToolingApiDistribution toolingApi
-        final BasicGradleDistribution gradle
+        final GradleDistribution gradle
 
-        Permutation(ToolingApiDistribution toolingApi, BasicGradleDistribution gradle) {
+        Permutation(ToolingApiDistribution toolingApi, GradleDistribution gradle) {
             this.toolingApi = toolingApi
             this.gradle = gradle
         }
 
         @Override
         protected String getDisplayName() {
-            return "${displayName(toolingApi)} -> ${displayName(gradle)}"
+            return "${displayName(GradleVersion.version(toolingApi.version))} -> ${displayName(gradle.version)}"
         }
 
-        private String displayName(dist) {
-            if (dist.version == GradleVersion.current().version) {
+        private String displayName(GradleVersion version) {
+            if (version == GradleVersion.current()) {
                 return "current"
             }
-            return dist.version
+            return version.version
         }
 
         @Override
@@ -94,11 +94,11 @@ class ToolingApiCompatibilitySuiteRunner extends AbstractCompatibilityTestRunner
                 return false
             }
             MinTargetGradleVersion minTargetGradleVersion = target.getAnnotation(MinTargetGradleVersion)
-            if (minTargetGradleVersion && GradleVersion.version(gradle.version) < extractVersion(minTargetGradleVersion)) {
+            if (minTargetGradleVersion && gradle.version < extractVersion(minTargetGradleVersion)) {
                 return false
             }
             MaxTargetGradleVersion maxTargetGradleVersion = target.getAnnotation(MaxTargetGradleVersion)
-            if (maxTargetGradleVersion && GradleVersion.version(gradle.version) > extractVersion(maxTargetGradleVersion)) {
+            if (maxTargetGradleVersion && gradle.version > extractVersion(maxTargetGradleVersion)) {
                 return false
             }
 
@@ -146,10 +146,9 @@ class ToolingApiCompatibilitySuiteRunner extends AbstractCompatibilityTestRunner
             sharedClassLoader.allowPackage('org.codehaus.groovy')
             sharedClassLoader.allowPackage('spock')
             sharedClassLoader.allowPackage('org.spockframework')
-            sharedClassLoader.allowClass(TestFile)
             sharedClassLoader.allowClass(SetSystemProperties)
             sharedClassLoader.allowPackage('org.gradle.integtests.fixtures')
-            sharedClassLoader.allowPackage('org.gradle.tests.fixtures')
+            sharedClassLoader.allowPackage('org.gradle.test.fixtures')
             sharedClassLoader.allowClass(OperatingSystem)
             sharedClassLoader.allowClass(Requires)
             sharedClassLoader.allowClass(TestPrecondition)
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistributionResolver.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistributionResolver.groovy
index baeea57..8d1015a 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistributionResolver.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistributionResolver.groovy
@@ -16,23 +16,26 @@
 
 package org.gradle.integtests.tooling.fixture
 
-import org.gradle.util.HelperUtil
-import org.gradle.api.internal.project.ProjectInternalServiceRegistry
-import org.gradle.api.internal.artifacts.DependencyResolutionServices
+import org.gradle.StartParameter
 import org.gradle.api.artifacts.Configuration
 import org.gradle.api.artifacts.Dependency
-import org.gradle.api.internal.project.TopLevelBuildServiceRegistry
-import org.gradle.StartParameter
+import org.gradle.api.internal.artifacts.DependencyResolutionServices
 import org.gradle.api.internal.project.GlobalServicesRegistry
-import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.api.internal.project.ProjectInternalServiceRegistry
+import org.gradle.api.internal.project.TopLevelBuildServiceRegistry
+import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
+import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext
+import org.gradle.util.HelperUtil
 
 class ToolingApiDistributionResolver {
     private final DependencyResolutionServices resolutionServices
     private final Map<String, ToolingApiDistribution> distributions = [:]
-    private final GradleDistribution currentGradleDistribution = new GradleDistribution()
+    private final IntegrationTestBuildContext buildContext = new IntegrationTestBuildContext()
+    private boolean useExternalToolingApiDistribution = false;
+
     ToolingApiDistributionResolver() {
         resolutionServices = createResolutionServices()
-        resolutionServices.resolveRepositoryHandler.maven { url currentGradleDistribution.libsRepo.toURI().toURL() }
+        resolutionServices.resolveRepositoryHandler.maven { url buildContext.libsRepo.toURI().toURL() }
     }
 
     ToolingApiDistributionResolver withRepository(String repositoryUrl) {
@@ -58,15 +61,22 @@ class ToolingApiDistributionResolver {
     }
 
     private boolean useToolingApiFromTestClasspath(String toolingApiVersion) {
-        toolingApiVersion == currentGradleDistribution.version && System.getProperty("org.gradle.integtest.toolingApiFromTestClasspath", "true") == "true"
+        !useExternalToolingApiDistribution &&
+        toolingApiVersion == buildContext.version.version &&
+                GradleContextualExecuter.embedded
     }
 
     private DependencyResolutionServices createResolutionServices() {
         GlobalServicesRegistry globalRegistry = new GlobalServicesRegistry()
         StartParameter startParameter = new StartParameter()
-        startParameter.gradleUserHomeDir = currentGradleDistribution.userHomeDir
+        startParameter.gradleUserHomeDir = new IntegrationTestBuildContext().gradleUserHomeDir
         TopLevelBuildServiceRegistry topLevelRegistry = new TopLevelBuildServiceRegistry(globalRegistry, startParameter)
         ProjectInternalServiceRegistry projectRegistry = new ProjectInternalServiceRegistry(topLevelRegistry, HelperUtil.createRootProject())
         projectRegistry.get(DependencyResolutionServices)
     }
+
+    ToolingApiDistributionResolver withExternalToolingApiDistribution() {
+        this.useExternalToolingApiDistribution = true
+        this
+    }
 }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiSpecification.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiSpecification.groovy
index 750cd12..8d3077a 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiSpecification.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiSpecification.groovy
@@ -15,8 +15,11 @@
  */
 package org.gradle.integtests.tooling.fixture
 
-import org.gradle.integtests.fixtures.BasicGradleDistribution
-import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.executer.GradleDistribution
+import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext
+import org.gradle.integtests.fixtures.executer.UnderDevelopmentGradleDistribution
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.tooling.GradleConnector
 import org.gradle.util.GradleVersion
 import org.gradle.util.SetSystemProperties
@@ -26,27 +29,28 @@ import org.junit.runner.RunWith
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import spock.lang.Specification
-import org.gradle.util.TestFile
 
 @RunWith(ToolingApiCompatibilitySuiteRunner)
 abstract class ToolingApiSpecification extends Specification {
     static final Logger LOGGER = LoggerFactory.getLogger(ToolingApiSpecification)
     @Rule public final SetSystemProperties sysProperties = new SetSystemProperties()
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    final ToolingApi toolingApi = new ToolingApi(dist)
-    private static final ThreadLocal<BasicGradleDistribution> VERSION = new ThreadLocal<BasicGradleDistribution>()
+    @Rule public final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
+    final GradleDistribution dist = new UnderDevelopmentGradleDistribution()
+    final IntegrationTestBuildContext buildContext = new IntegrationTestBuildContext()
+    final ToolingApi toolingApi = new ToolingApi(dist, temporaryFolder)
+    private static final ThreadLocal<GradleDistribution> VERSION = new ThreadLocal<GradleDistribution>()
 
-    static void selectTargetDist(BasicGradleDistribution version) {
+    static void selectTargetDist(GradleDistribution version) {
         VERSION.set(version)
     }
 
-    static BasicGradleDistribution getTargetDist() {
+    static GradleDistribution getTargetDist() {
         VERSION.get()
     }
 
     void setup() {
         def consumerGradle = GradleVersion.current()
-        def target = GradleVersion.version(VERSION.get().version)
+        def target = GradleVersion.version(VERSION.get().version.version)
         LOGGER.info(" Using Tooling API consumer ${consumerGradle}, provider ${target}")
         boolean accept = accept(consumerGradle, target)
         if (!accept) {
@@ -55,8 +59,7 @@ abstract class ToolingApiSpecification extends Specification {
         this.toolingApi.withConnector {
             if (consumerGradle.version != target.version) {
                 LOGGER.info("Overriding daemon tooling API provider to use installation: " + target);
-                def targetGradle = dist.previousVersion(target.version)
-                it.useInstallation(new File(targetGradle.gradleHomeDir.absolutePath))
+                it.useInstallation(new File(getTargetDist().gradleHomeDir.absolutePath))
                 it.embedded(false)
             }
         }
@@ -86,7 +89,7 @@ abstract class ToolingApiSpecification extends Specification {
     }
 
     TestFile getProjectDir() {
-        dist.testDir
+        temporaryFolder.testDirectory
     }
 
     TestFile getBuildFile() {
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m3/ToolingApiEclipseModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m3/ToolingApiEclipseModelCrossVersionSpec.groovy
index c1e8aba..050e653 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m3/ToolingApiEclipseModelCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m3/ToolingApiEclipseModelCrossVersionSpec.groovy
@@ -23,7 +23,7 @@ import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject
 class ToolingApiEclipseModelCrossVersionSpec extends ToolingApiSpecification {
 
     def "can build the eclipse model for a java project"() {
-        def projectDir = dist.testDir
+        
         projectDir.file('build.gradle').text = '''
 apply plugin: 'java'
 description = 'this is a project'
@@ -80,7 +80,6 @@ description = 'this is a project'
 
     def "does not run any tasks when fetching model"() {
         when:
-        def projectDir = dist.testDir
         projectDir.file('build.gradle').text = '''
 apply plugin: 'java'
 gradle.taskGraph.beforeTask { throw new RuntimeException() }
@@ -92,7 +91,7 @@ gradle.taskGraph.beforeTask { throw new RuntimeException() }
     }
 
     def "can build the eclipse source directories for a java project"() {
-        def projectDir = dist.testDir
+        
         projectDir.file('build.gradle').text = "apply plugin: 'java'"
 
         projectDir.create {
@@ -142,7 +141,7 @@ gradle.taskGraph.beforeTask { throw new RuntimeException() }
     }
 
     def "can build the eclipse external dependencies for a java project"() {
-        def projectDir = dist.testDir
+        
         projectDir.file('settings.gradle').text = '''
 include "a"
 rootProject.name = 'root'
@@ -173,7 +172,7 @@ dependencies {
     //TODO SF: write a test that checks if minimal project has necessary project dependencies
 
     def "can build the minimal Eclipse model for a java project with the idea plugin applied"() {
-        def projectDir = dist.testDir
+        
         projectDir.file('build.gradle').text = '''
 apply plugin: 'java'
 apply plugin: 'idea'
@@ -191,7 +190,7 @@ dependencies {
     }
 
     def "can build the eclipse project dependencies for a java project"() {
-        def projectDir = dist.testDir
+        
         projectDir.file('settings.gradle').text = '''
 include "a", "a:b"
 rootProject.name = 'root'
@@ -232,7 +231,7 @@ project(':a') {
     }
 
     def "can build project dependencies with targetProject references for complex scenarios"() {
-        def projectDir = dist.testDir
+        
         projectDir.file('settings.gradle').text = '''
 include "c", "a", "a:b"
 rootProject.name = 'root'
@@ -271,7 +270,7 @@ project(':c') {
     }
 
     def "can build the eclipse project hierarchy for a multi-project build"() {
-        def projectDir = dist.testDir
+        
         projectDir.file('settings.gradle').text = '''
             include "child1", "child2", "child1:grandChild1"
             rootProject.name = 'root'
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseLinkedResourcesCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseLinkedResourcesCrossVersionSpec.groovy
index 3daeaef..e7350ca 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseLinkedResourcesCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseLinkedResourcesCrossVersionSpec.groovy
@@ -28,8 +28,8 @@ import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject
 @MinTargetGradleVersion('1.0-milestone-4')
 class ToolingApiEclipseLinkedResourcesCrossVersionSpec extends ToolingApiSpecification {
     def "can build linked resources"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = '''
+
+        file('build.gradle').text = '''
 apply plugin: 'java'
 apply plugin: 'eclipse'
 
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseMinimalModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseMinimalModelCrossVersionSpec.groovy
index 2649973..f9fdfd3 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseMinimalModelCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseMinimalModelCrossVersionSpec.groovy
@@ -22,9 +22,9 @@ import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject
 @MinTargetGradleVersion('1.0-milestone-4')
 class ToolingApiEclipseMinimalModelCrossVersionSpec extends ToolingApiSpecification {
     def "minimal Eclipse model does not attempt to resolve external dependencies"() {
-        def projectDir = dist.testDir
-        projectDir.file('settings.gradle').text = 'include "child"'
-        projectDir.file('build.gradle').text = '''
+
+        file('settings.gradle').text = 'include "child"'
+        file('build.gradle').text = '''
 apply plugin: 'java'
 dependencies {
     compile project(':child')
@@ -49,8 +49,7 @@ project(':child') {
     }
 
     def "minimal Eclipse model does not attempt to call any tasks"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = '''
+        file('build.gradle').text = '''
 apply plugin: 'java'
 
 sourceSets.main.output.dir "$buildDir/foo", builtBy: 'generateResources'
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/EclipseModelWithFlatRepoCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/EclipseModelWithFlatRepoCrossVersionSpec.groovy
index f85d816..cc75efd 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/EclipseModelWithFlatRepoCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/EclipseModelWithFlatRepoCrossVersionSpec.groovy
@@ -19,12 +19,10 @@ package org.gradle.integtests.tooling.m5
 import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.gradle.tooling.model.eclipse.EclipseProject
-import org.gradle.util.TestFile
 import spock.lang.Issue
 
 @MinTargetGradleVersion('1.0-milestone-5')
 class EclipseModelWithFlatRepoCrossVersionSpec extends ToolingApiSpecification {
-    TestFile projectDir = dist.testDir
 
     @Issue("GRADLE-1621")
     def "can get Eclipse model for project with flatDir repo and external dependency without source Jar"() {
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildExecutionCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildExecutionCrossVersionSpec.groovy
index b5f44c1..8424762 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildExecutionCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildExecutionCrossVersionSpec.groovy
@@ -28,7 +28,7 @@ import org.gradle.tooling.model.eclipse.EclipseProject
 @MinTargetGradleVersion('1.0-milestone-5')
 class ToolingApiBuildExecutionCrossVersionSpec extends ToolingApiSpecification {
     def "can build the set of tasks for a project"() {
-        dist.testFile('build.gradle') << '''
+        file('build.gradle') << '''
 task a {
    description = 'this is task a'
 }
@@ -50,8 +50,8 @@ task c
     }
 
     def "can execute a build for a project"() {
-        dist.testFile('settings.gradle') << 'rootProject.name="test"'
-        dist.testFile('build.gradle') << '''
+        file('settings.gradle') << 'rootProject.name="test"'
+        file('build.gradle') << '''
 apply plugin: 'java'
 '''
         when:
@@ -62,7 +62,7 @@ apply plugin: 'java'
         }
 
         then:
-        dist.testFile('build/libs/test.jar').assertIsFile()
+        file('build/libs/test.jar').assertIsFile()
 
         when:
         withConnection { connection ->
@@ -74,11 +74,11 @@ apply plugin: 'java'
         }
 
         then:
-        dist.testFile('build/libs/test.jar').assertDoesNotExist()
+        file('build/libs/test.jar').assertDoesNotExist()
     }
 
     def "receives progress while the build is executing"() {
-        dist.testFile('build.gradle') << '''
+        file('build.gradle') << '''
 System.out.println 'this is stdout'
 System.err.println 'this is stderr'
 '''
@@ -98,7 +98,7 @@ System.err.println 'this is stderr'
     }
 
     def "tooling api reports build failure"() {
-        dist.testFile('build.gradle') << 'broken'
+        file('build.gradle') << 'broken'
 
         when:
         withConnection { connection ->
@@ -112,7 +112,7 @@ System.err.println 'this is stderr'
     }
 
     def "can build the set of tasks for an Eclipse project"() {
-        dist.testFile('build.gradle') << '''
+        file('build.gradle') << '''
 task a {
    description = 'this is task a'
 }
@@ -134,7 +134,7 @@ task c
     }
 
     def "does not resolve dependencies when building the set of tasks for a project"() {
-        dist.testFile('build.gradle') << '''
+        file('build.gradle') << '''
 apply plugin: 'java'
 dependencies {
     compile files { throw new RuntimeException('broken') }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildableEclipseModelFixesCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildableEclipseModelFixesCrossVersionSpec.groovy
index 848f522..3e00e10 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildableEclipseModelFixesCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildableEclipseModelFixesCrossVersionSpec.groovy
@@ -28,7 +28,7 @@ class ToolingApiBuildableEclipseModelFixesCrossVersionSpec extends ToolingApiSpe
     @Issue("GRADLE-1529")
     //this is just one of the ways of fixing the problem. See the issue for details
     def "should not show not executable tasks"() {
-        dist.testFile('build.gradle') << '''
+        file('build.gradle') << '''
 task a
 task b
 '''
@@ -46,14 +46,13 @@ task b
     @Issue("GRADLE-1529")
     //this is just one of the ways of fixing the problem. See the issue for details
     def "should hide not executable tasks when necessary for a multi module build"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = '''
+        file('build.gradle').text = '''
 project(':api') {
     apply plugin: 'java'
     apply plugin: 'eclipse'
 }
 '''
-        projectDir.file('settings.gradle').text = "include 'api', 'impl'"
+        file('settings.gradle').text = "include 'api', 'impl'"
 
         when:
         EclipseProject eclipseProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiEclipseModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiEclipseModelCrossVersionSpec.groovy
index 6ab846d..88c223c 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiEclipseModelCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiEclipseModelCrossVersionSpec.groovy
@@ -24,8 +24,8 @@ import org.gradle.tooling.model.eclipse.EclipseProject
 @MinTargetGradleVersion('1.0-milestone-3')
 class ToolingApiEclipseModelCrossVersionSpec extends ToolingApiSpecification {
     def "eclipse project has access to gradle project and its tasks"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = """
+
+        file('build.gradle').text = """
 subprojects {
     apply plugin: 'java'
 }
@@ -36,7 +36,7 @@ project(':impl') {
     task implTask {}
 }
 """
-        projectDir.file('settings.gradle').text = "include 'api', 'impl'; rootProject.name = 'root'"
+        file('settings.gradle').text = "include 'api', 'impl'; rootProject.name = 'root'"
 
         when:
         def root = withConnection { it.getModel(EclipseProject.class) }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiGradleProjectCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiGradleProjectCrossVersionSpec.groovy
index 5ba64c7..74a52b3 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiGradleProjectCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiGradleProjectCrossVersionSpec.groovy
@@ -26,7 +26,7 @@ import org.gradle.tooling.model.GradleTask
 class ToolingApiGradleProjectCrossVersionSpec extends ToolingApiSpecification {
 
     def "provides tasks of a project"() {
-        dist.testFile('build.gradle') << '''
+        file('build.gradle') << '''
 task a {
    description = 'this is task a'
 }
@@ -48,8 +48,8 @@ task c
     }
 
     def "provides hierarchy"() {
-        dist.testFile('settings.gradle') << "include 'a', 'a:b', 'a:c', 'a:c:d'"
-        dist.testFile('build.gradle') << '''
+        file('settings.gradle') << "include 'a', 'a:b', 'a:c', 'a:c:d'"
+        file('build.gradle') << '''
 task rootTask
 project (':a') { description = 'A rocks!' }
 '''
@@ -74,8 +74,8 @@ project (':a') { description = 'A rocks!' }
     }
 
     def "can provide tasks for hierarchical project"() {
-        dist.testFile('settings.gradle') << "include 'a', 'a:b', 'a:c'"
-        dist.testFile('build.gradle') << '''
+        file('settings.gradle') << "include 'a', 'a:b', 'a:c'"
+        file('build.gradle') << '''
 task rootTask
 project(':a') { task taskA }
 project(':a:b') { task taskAB }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiHonorsProjectCustomizationsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiHonorsProjectCustomizationsCrossVersionSpec.groovy
index 314defe..49f4683 100755
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiHonorsProjectCustomizationsCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiHonorsProjectCustomizationsCrossVersionSpec.groovy
@@ -26,8 +26,8 @@ import org.gradle.tooling.model.eclipse.EclipseProject
 class ToolingApiHonorsProjectCustomizationsCrossVersionSpec extends ToolingApiSpecification {
 
     def "should honour reconfigured project names"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = '''
+
+        file('build.gradle').text = '''
 allprojects {
     apply plugin: 'java'
     apply plugin: 'eclipse'
@@ -41,7 +41,7 @@ project(':impl') {
     eclipse.project.name = 'gradle-impl'
 }
 '''
-        projectDir.file('settings.gradle').text = "include 'api', 'impl'"
+        file('settings.gradle').text = "include 'api', 'impl'"
 
         when:
         EclipseProject eclipseProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
@@ -54,13 +54,12 @@ project(':impl') {
     }
 
     def "should deduplicate project names"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = '''
+        file('build.gradle').text = '''
 allprojects {
     apply plugin: 'java'
 }
 '''
-        projectDir.file('settings.gradle').text = "include 'services:api', 'contrib:api'"
+        file('settings.gradle').text = "include 'services:api', 'contrib:api'"
 
         when:
         EclipseProject eclipseProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
@@ -72,8 +71,7 @@ allprojects {
     }
 
     def "can have overlapping source and resource directories"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = '''
+        file('build.gradle').text = '''
 apply plugin: 'java'
 apply plugin: 'eclipse'
 
@@ -107,8 +105,7 @@ sourceSets {
     }
 
     def "can enable download of Javadoc for external dependencies"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = '''
+        file('build.gradle').text = '''
 apply plugin: 'java'
 apply plugin: 'eclipse'
 repositories { mavenCentral() }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiIdeaModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiIdeaModelCrossVersionSpec.groovy
index b30ac63..954aaee 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiIdeaModelCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiIdeaModelCrossVersionSpec.groovy
@@ -15,10 +15,10 @@
  */
 package org.gradle.integtests.tooling.m5
 
-import org.gradle.integtests.fixtures.MavenFileRepository
 import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
 import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.test.fixtures.maven.MavenFileRepository
 import org.gradle.tooling.model.idea.*
 
 @MinToolingApiVersion('1.0-milestone-5')
@@ -26,12 +26,12 @@ import org.gradle.tooling.model.idea.*
 class ToolingApiIdeaModelCrossVersionSpec extends ToolingApiSpecification {
 
     def "builds the model even if idea plugin not applied"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = '''
+        
+        file('build.gradle').text = '''
 apply plugin: 'java'
 description = 'this is a project'
 '''
-        projectDir.file('settings.gradle').text = 'rootProject.name = \"test project\"'
+        file('settings.gradle').text = 'rootProject.name = \"test project\"'
 
         when:
         IdeaProject project = withConnection { connection -> connection.getModel(IdeaProject.class) }
@@ -46,8 +46,8 @@ description = 'this is a project'
     }
 
     def "provides basic project information"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = """
+        
+        file('build.gradle').text = """
 apply plugin: 'java'
 apply plugin: 'idea'
 
@@ -66,13 +66,13 @@ idea.project {
     }
 
     def "provides all modules"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = '''
+        
+        file('build.gradle').text = '''
 subprojects {
     apply plugin: 'java'
 }
 '''
-        projectDir.file('settings.gradle').text = "include 'api', 'impl'"
+        file('settings.gradle').text = "include 'api', 'impl'"
 
         when:
         IdeaProject project = withConnection { connection -> connection.getModel(IdeaProject.class) }
@@ -84,8 +84,8 @@ subprojects {
     }
 
     def "provides basic module information"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = """
+        
+        file('build.gradle').text = """
 apply plugin: 'java'
 apply plugin: 'idea'
 
@@ -108,13 +108,13 @@ idea.module.testOutputDir = file('someTestDir')
         module.description == null
 
         !module.compilerOutput.inheritOutputDirs
-        module.compilerOutput.outputDir == projectDir.file('someDir')
-        module.compilerOutput.testOutputDir == projectDir.file('someTestDir')
+        module.compilerOutput.outputDir == file('someDir')
+        module.compilerOutput.testOutputDir == file('someTestDir')
     }
 
     def "provides source dir information"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = "apply plugin: 'java'"
+        
+        file('build.gradle').text = "apply plugin: 'java'"
 
         projectDir.create {
             src {
@@ -136,17 +136,17 @@ idea.module.testOutputDir = file('someTestDir')
 
         then:
         root.sourceDirectories.size() == 2
-        root.sourceDirectories.any { it.directory == projectDir.file('src/main/java') }
-        root.sourceDirectories.any { it.directory == projectDir.file('src/main/resources') }
+        root.sourceDirectories.any { it.directory == file('src/main/java') }
+        root.sourceDirectories.any { it.directory == file('src/main/resources') }
 
         root.testDirectories.size() == 2
-        root.testDirectories.any { it.directory == projectDir.file('src/test/java') }
-        root.testDirectories.any { it.directory == projectDir.file('src/test/resources') }
+        root.testDirectories.any { it.directory == file('src/test/java') }
+        root.testDirectories.any { it.directory == file('src/test/resources') }
     }
 
     def "provides exclude dir information"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = """
+        
+        file('build.gradle').text = """
 apply plugin: 'java'
 apply plugin: 'idea'
 
@@ -162,15 +162,15 @@ idea.module.excludeDirs += file('foo')
     }
 
     def "provides dependencies"() {
-        def projectDir = dist.testDir
-        def fakeRepo = projectDir.file("repo")
+        
+        def fakeRepo = file("repo")
 
         def dependency = new MavenFileRepository(fakeRepo).module("foo.bar", "coolLib", 1.0)
         dependency.artifact(classifier: 'sources')
         dependency.artifact(classifier: 'javadoc')
         dependency.publish()
 
-        projectDir.file('build.gradle').text = """
+        file('build.gradle').text = """
 subprojects {
     apply plugin: 'java'
 }
@@ -190,7 +190,7 @@ project(':impl') {
     idea.module.downloadJavadoc = true
 }
 """
-        projectDir.file('settings.gradle').text = "include 'api', 'impl'"
+        file('settings.gradle').text = "include 'api', 'impl'"
 
         when:
         IdeaProject project = withConnection { connection -> connection.getModel(IdeaProject.class) }
@@ -217,8 +217,8 @@ project(':impl') {
     }
 
     def "makes sure module names are unique"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = """
+        
+        file('build.gradle').text = """
 subprojects {
     apply plugin: 'java'
 }
@@ -235,7 +235,7 @@ project(':contrib:impl') {
     }
 }
 """
-        projectDir.file('settings.gradle').text = "include 'api', 'impl', 'contrib:api', 'contrib:impl'"
+        file('settings.gradle').text = "include 'api', 'impl', 'contrib:api', 'contrib:impl'"
 
         when:
         IdeaProject project = withConnection { connection -> connection.getModel(IdeaProject.class) }
@@ -252,8 +252,8 @@ project(':contrib:impl') {
     }
 
     def "module has access to gradle project and its tasks"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = """
+        
+        file('build.gradle').text = """
 subprojects {
     apply plugin: 'java'
 }
@@ -264,7 +264,7 @@ project(':impl') {
     task implTask {}
 }
 """
-        projectDir.file('settings.gradle').text = "include 'api', 'impl'; rootProject.name = 'root'"
+        file('settings.gradle').text = "include 'api', 'impl'; rootProject.name = 'root'"
 
         when:
         IdeaProject project = withConnection { connection -> connection.getModel(IdeaProject.class) }
@@ -281,9 +281,9 @@ project(':impl') {
     }
 
     def "offline model should not resolve external dependencies"() {
-        def projectDir = dist.testDir
+        
 
-        projectDir.file('build.gradle').text = """
+        file('build.gradle').text = """
 subprojects {
     apply plugin: 'java'
 }
@@ -297,7 +297,7 @@ project(':impl') {
     }
 }
 """
-        projectDir.file('settings.gradle').text = "include 'api', 'impl'"
+        file('settings.gradle').text = "include 'api', 'impl'"
 
         when:
         BasicIdeaProject project = withConnection { connection -> connection.getModel(BasicIdeaProject.class) }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiModelCrossVersionSpec.groovy
index 8f16de2..8cd1688 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiModelCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiModelCrossVersionSpec.groovy
@@ -26,7 +26,7 @@ import org.gradle.tooling.model.GradleProject
 @MinTargetGradleVersion('1.0-milestone-5')
 class ToolingApiModelCrossVersionSpec extends ToolingApiSpecification {
     def "receives progress while the model is building"() {
-        dist.testFile('build.gradle') << '''
+        file('build.gradle') << '''
 System.out.println 'this is stdout'
 System.err.println 'this is stderr'
 '''
@@ -47,7 +47,7 @@ System.err.println 'this is stderr'
     }
 
     def "tooling api reports failure to build model"() {
-        dist.testFile('build.gradle') << 'broken'
+        file('build.gradle') << 'broken'
 
         when:
         withConnection { connection ->
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiReceivingStandardStreamsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiReceivingStandardStreamsCrossVersionSpec.groovy
index 3fb806d..e7fd795 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiReceivingStandardStreamsCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiReceivingStandardStreamsCrossVersionSpec.groovy
@@ -31,7 +31,7 @@ class ToolingApiReceivingStandardStreamsCrossVersionSpec extends ToolingApiSpeci
     }
 
     def "receives standard streams while the build is executing"() {
-        dist.testFile('build.gradle') << '''
+        file('build.gradle') << '''
 System.out.println 'this is stdout'
 System.err.println 'this is stderr'
 '''
@@ -52,7 +52,7 @@ System.err.println 'this is stderr'
     }
 
     def "receives standard streams while the model is building"() {
-        dist.testFile('build.gradle') << '''
+        file('build.gradle') << '''
 System.out.println 'this is stdout'
 System.err.println 'this is stderr'
 '''
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/BuildEnvironmentModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/BuildEnvironmentModelCrossVersionSpec.groovy
index cbfb1ff..14d9260 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/BuildEnvironmentModelCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/BuildEnvironmentModelCrossVersionSpec.groovy
@@ -31,7 +31,7 @@ class BuildEnvironmentModelCrossVersionSpec extends ToolingApiSpecification {
         BuildEnvironment model = withConnection { it.getModel(BuildEnvironment.class) }
 
         then:
-        model.gradle.gradleVersion == targetDist.version
+        model.gradle.gradleVersion == targetDist.version.version
         model.java.javaHome
         !model.java.jvmArguments.empty
     }
@@ -40,7 +40,7 @@ class BuildEnvironmentModelCrossVersionSpec extends ToolingApiSpecification {
         given:
         toolingApi.isEmbedded = false //cannot be run in embedded mode
 
-        dist.file('build.gradle') <<
+        file('build.gradle') <<
             "project.description = java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.join('##')"
 
         when:
@@ -54,7 +54,7 @@ class BuildEnvironmentModelCrossVersionSpec extends ToolingApiSpecification {
 
     def "informs about java home as in the build script"() {
         given:
-        dist.file('build.gradle') << """
+        file('build.gradle') << """
         description = Jvm.current().javaHome.toString()
         """
 
@@ -68,7 +68,7 @@ class BuildEnvironmentModelCrossVersionSpec extends ToolingApiSpecification {
 
     def "informs about gradle version as in the build script"() {
         given:
-        dist.file('build.gradle') << "description = GradleVersion.current().getVersion()"
+        file('build.gradle') << "description = GradleVersion.current().getVersion()"
 
         when:
         BuildEnvironment env = withConnection { it.getModel(BuildEnvironment.class) }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ConsumingStandardInputCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ConsumingStandardInputCrossVersionSpec.groovy
index 1591c26..bf0e97c 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ConsumingStandardInputCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ConsumingStandardInputCrossVersionSpec.groovy
@@ -35,7 +35,7 @@ class ConsumingStandardInputCrossVersionSpec extends ToolingApiSpecification {
     @Timeout(90)
     def "consumes input when building model"() {
         given:
-        dist.file('build.gradle')  << """
+        file('build.gradle')  << """
 description = System.in.text
 """
         when:
@@ -52,7 +52,7 @@ description = System.in.text
     @Timeout(90)
     def "works well if the standard input configured with null"() {
         given:
-        dist.file('build.gradle')  << """
+        file('build.gradle')  << """
 description = System.in.text
 """
         when:
@@ -69,7 +69,7 @@ description = System.in.text
     @Timeout(90)
     def "does not consume input when not explicitly provided"() {
         given:
-        dist.file('build.gradle')  << """
+        file('build.gradle')  << """
 description = "empty" + System.in.text
 """
         when:
@@ -85,7 +85,7 @@ description = "empty" + System.in.text
     @Timeout(90)
     def "consumes input when running tasks"() {
         given:
-        dist.file('build.gradle') << """
+        file('build.gradle') << """
 task createFile << {
     file('input.txt') << System.in.text
 }
@@ -99,6 +99,6 @@ task createFile << {
         }
 
         then:
-        dist.file('input.txt').text == "Hello world!"
+        file('input.txt').text == "Hello world!"
     }
 }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/GradlePropertiesCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/GradlePropertiesCrossVersionSpec.groovy
index bc13fd3..c13685a 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/GradlePropertiesCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/GradlePropertiesCrossVersionSpec.groovy
@@ -35,11 +35,11 @@ class GradlePropertiesCrossVersionSpec extends ToolingApiSpecification {
     }
 
     def "tooling api honours jvm args specified in gradle.properties"() {
-        dist.file('build.gradle') << """
+        file('build.gradle') << """
 assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.contains('-Xmx16m')
 assert System.getProperty('some-prop') == 'some-value'
 """
-        dist.file('gradle.properties') << "org.gradle.jvmargs=-Dsome-prop=some-value -Xmx16m"
+        file('gradle.properties') << "org.gradle.jvmargs=-Dsome-prop=some-value -Xmx16m"
 
         when:
         BuildEnvironment env = toolingApi.withConnection { connection ->
@@ -56,9 +56,9 @@ assert System.getProperty('some-prop') == 'some-value'
         File javaHome = AvailableJavaHomes.bestAlternative
         String javaHomePath = TextUtil.escapeString(javaHome.canonicalPath)
 
-        dist.file('build.gradle') << "assert new File(System.getProperty('java.home')).canonicalPath.startsWith('$javaHomePath')"
+        file('build.gradle') << "assert new File(System.getProperty('java.home')).canonicalPath.startsWith('$javaHomePath')"
 
-        dist.file('gradle.properties') << "org.gradle.java.home=$javaHomePath"
+        file('gradle.properties') << "org.gradle.java.home=$javaHomePath"
 
         when:
         BuildEnvironment env = toolingApi.withConnection { connection ->
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/JavaConfigurabilityCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/JavaConfigurabilityCrossVersionSpec.groovy
index 8ec471f..d822b72 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/JavaConfigurabilityCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/JavaConfigurabilityCrossVersionSpec.groovy
@@ -63,11 +63,11 @@ class JavaConfigurabilityCrossVersionSpec extends ToolingApiSpecification {
     }
 
     def "tooling api provided jvm args take precedence over gradle.properties"() {
-        dist.file('build.gradle') << """
+        file('build.gradle') << """
 assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.contains('-Xmx23m')
 assert System.getProperty('some-prop') == 'BBB'
 """
-        dist.file('gradle.properties') << "org.gradle.jvmargs=-Dsome-prop=AAA -Xmx16m"
+        file('gradle.properties') << "org.gradle.jvmargs=-Dsome-prop=AAA -Xmx16m"
 
         when:
         def model = withConnection {
@@ -82,7 +82,7 @@ assert System.getProperty('some-prop') == 'BBB'
 
     def "customized java args are reflected in the inputArguments and the build model"() {
         given:
-        dist.file('build.gradle') <<
+        file('build.gradle') <<
                 "project.description = java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.join('##')"
 
         when:
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiEclipseModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiEclipseModelCrossVersionSpec.groovy
index 50a0318..68a2b9e 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiEclipseModelCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiEclipseModelCrossVersionSpec.groovy
@@ -24,7 +24,6 @@ import org.gradle.tooling.model.eclipse.EclipseProject
 @MinTargetGradleVersion('1.0-milestone-8')
 class ToolingApiEclipseModelCrossVersionSpec extends ToolingApiSpecification {
     def "can customise model late in the configuration phase"() {
-        def projectDir = dist.testDir
         projectDir.file('build.gradle').text = """
 apply plugin: 'java'
 
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiLoggingCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiLoggingCrossVersionSpec.groovy
index 03a8bc3..b82dc6b 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiLoggingCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiLoggingCrossVersionSpec.groovy
@@ -38,7 +38,7 @@ class ToolingApiLoggingCrossVersionSpec extends ToolingApiSpecification {
     def "logs necessary information when verbose"() {
         toolingApi.verboseLogging = true
 
-        dist.file("build.gradle") << """
+        file("build.gradle") << """
 System.err.println "sys err logging xxx"
 
 println "println logging yyy"
@@ -83,7 +83,7 @@ project.logger.debug("debug logging yyy");
     def "logs necessary information"() {
         toolingApi.verboseLogging = false
 
-        dist.file("build.gradle") << """
+        file("build.gradle") << """
 System.err.println "sys err logging xxx"
 
 println "println logging yyy"
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/VersionOnlyBuildEnvironmentCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/VersionOnlyBuildEnvironmentCrossVersionSpec.groovy
index eefd53c..b8757d2 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/VersionOnlyBuildEnvironmentCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/VersionOnlyBuildEnvironmentCrossVersionSpec.groovy
@@ -33,7 +33,7 @@ class VersionOnlyBuildEnvironmentCrossVersionSpec extends ToolingApiSpecificatio
         BuildEnvironment model = withConnection { it.getModel(BuildEnvironment.class) }
 
         then:
-        model.gradle.gradleVersion == targetDist.version
+        model.gradle.gradleVersion == targetDist.version.version
     }
 
     def "fails gracefully for other info"() {
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m9/M9JavaConfigurabilityCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m9/M9JavaConfigurabilityCrossVersionSpec.groovy
index 3146969..9e3961a 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m9/M9JavaConfigurabilityCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m9/M9JavaConfigurabilityCrossVersionSpec.groovy
@@ -28,7 +28,6 @@ import org.gradle.util.GradleVersion
 import spock.lang.IgnoreIf
 import spock.lang.Issue
 import spock.lang.Timeout
-import org.gradle.util.Jvm
 
 @MinToolingApiVersion('1.0-milestone-9')
 @MinTargetGradleVersion('1.0-milestone-8')
@@ -56,7 +55,7 @@ class M9JavaConfigurabilityCrossVersionSpec extends ToolingApiSpecification {
     @Issue("GRADLE-1799")
     @Timeout(25)
     def "promptly discovers when java is not a valid installation"() {
-        def dummyJdk = dist.file("wrong jdk location").createDir()
+        def dummyJdk = file("wrong jdk location").createDir()
 
         when:
         maybeFailWithConnection {
@@ -91,7 +90,7 @@ class M9JavaConfigurabilityCrossVersionSpec extends ToolingApiSpecification {
     @IgnoreIf({ AvailableJavaHomes.bestAlternative == null })
     def "customized java home is reflected in the java.home and the build model"() {
         given:
-        dist.file('build.gradle') << "project.description = new File(System.getProperty('java.home')).canonicalPath"
+        file('build.gradle') << "project.description = new File(System.getProperty('java.home')).canonicalPath"
 
         when:
         File javaHome = AvailableJavaHomes.bestAlternative
@@ -110,10 +109,10 @@ class M9JavaConfigurabilityCrossVersionSpec extends ToolingApiSpecification {
     def "tooling api provided java home takes precedence over gradle.properties"() {
         File javaHome = AvailableJavaHomes.bestAlternative
         String javaHomePath = TextUtil.escapeString(javaHome.canonicalPath)
-        File otherJava = Jvm.current().getJavaHome()
+        File otherJava = getOtherJava()
         String otherJavaPath = TextUtil.escapeString(otherJava.canonicalPath)
-        dist.file('build.gradle') << "assert new File(System.getProperty('java.home')).canonicalPath.startsWith('$javaHomePath')"
-        dist.file('gradle.properties') << "org.gradle.java.home=$otherJavaPath"
+        file('build.gradle') << "assert new File(System.getProperty('java.home')).canonicalPath.startsWith('$javaHomePath')"
+        file('gradle.properties') << "org.gradle.java.home=$otherJavaPath"
 
         when:
         def env = withConnection {
@@ -128,4 +127,15 @@ class M9JavaConfigurabilityCrossVersionSpec extends ToolingApiSpecification {
         env.java.javaHome == javaHome
         env.java.javaHome != otherJava
     }
+
+    // We use different ways of resolving JVM depending on the Gradle version
+    // this is necessary as we moved the Jvm class and dont ship the org.gradle.util.Jvm class with the
+    // toolingApi jar
+    File getOtherJava() {
+        if (GradleVersion.current().compareTo(GradleVersion.version("1.0-milestone-9")) > 0) {
+            return org.gradle.internal.jvm.Jvm.current().getJavaHome()
+        } else {
+            return org.gradle.util.Jvm.current().getJavaHome();
+        }
+    }
 }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r10rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r10rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy
index 4ebad65..08a0e2b 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r10rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r10rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy
@@ -35,7 +35,7 @@ class PassingCommandLineArgumentsCrossVersionSpec extends ToolingApiSpecificatio
     def "understands project properties for building model"() {
         given:
         toolingApi.verboseLogging = false //sanity check, see GRADLE-2226
-        dist.file("build.gradle") << """
+        file("build.gradle") << """
         description = project.getProperty('theDescription')
 """
 
@@ -50,7 +50,7 @@ class PassingCommandLineArgumentsCrossVersionSpec extends ToolingApiSpecificatio
 
     def "understands system properties"() {
         given:
-        dist.file("build.gradle") << """
+        file("build.gradle") << """
         task printProperty << {
             file('sysProperty.txt') << System.getProperty('sysProperty')
         }
@@ -62,12 +62,12 @@ class PassingCommandLineArgumentsCrossVersionSpec extends ToolingApiSpecificatio
         }
 
         then:
-        dist.file('sysProperty.txt').text.contains('welcomeToTheJungle')
+        file('sysProperty.txt').text.contains('welcomeToTheJungle')
     }
 
     def "can use custom build file"() {
         given:
-        dist.file("foo.gradle") << """
+        file("foo.gradle") << """
         task someCoolTask
 """
 
@@ -86,7 +86,7 @@ class PassingCommandLineArgumentsCrossVersionSpec extends ToolingApiSpecificatio
         toolingApi.isEmbedded = false
 
         given:
-        dist.file("build.gradle") << """
+        file("build.gradle") << """
         logger.debug("debugging stuff")
         logger.info("infoing stuff")
 """
@@ -129,8 +129,8 @@ class PassingCommandLineArgumentsCrossVersionSpec extends ToolingApiSpecificatio
 
     def "can overwrite project dir via build arguments"() {
         given:
-        dist.file('otherDir').createDir()
-        dist.file('build.gradle') << "assert projectDir.name.endsWith('otherDir')"
+        file('otherDir').createDir()
+        file('build.gradle') << "assert projectDir.name.endsWith('otherDir')"
 
         when:
         withConnection { 
@@ -143,8 +143,8 @@ class PassingCommandLineArgumentsCrossVersionSpec extends ToolingApiSpecificatio
 
     def "can overwrite gradle user home via build arguments"() {
         given:
-        dist.file('.myGradle').createDir()
-        dist.file('build.gradle') << "assert gradle.gradleUserHomeDir.name.endsWith('.myGradle')"
+        file('.myGradle').createDir()
+        file('build.gradle') << "assert gradle.gradleUserHomeDir.name.endsWith('.myGradle')"
 
         when:
         withConnection {
@@ -157,7 +157,7 @@ class PassingCommandLineArgumentsCrossVersionSpec extends ToolingApiSpecificatio
 
     def "can overwrite searchUpwards via build arguments"() {
         given:
-        dist.file('build.gradle') << "assert !gradle.startParameter.searchUpwards"
+        file('build.gradle') << "assert !gradle.startParameter.searchUpwards"
 
         when:
         toolingApi.withConnector { it.searchUpwards(true) }
@@ -171,7 +171,7 @@ class PassingCommandLineArgumentsCrossVersionSpec extends ToolingApiSpecificatio
 
     def "can overwrite task names via build arguments"() {
         given:
-        dist.file('build.gradle') << """
+        file('build.gradle') << """
 task foo << { assert false }
 task bar << { assert true }
 """
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r11rc1/DependencyMetaDataCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r11rc1/DependencyMetaDataCrossVersionSpec.groovy
index 2178eb2..997fc35 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r11rc1/DependencyMetaDataCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r11rc1/DependencyMetaDataCrossVersionSpec.groovy
@@ -15,10 +15,10 @@
  */
 package org.gradle.integtests.tooling.r11rc1
 
-import org.gradle.integtests.fixtures.MavenFileRepository
 import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
 import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.test.fixtures.maven.MavenFileRepository
 import org.gradle.tooling.model.ExternalDependency
 import org.gradle.tooling.model.eclipse.EclipseProject
 import org.gradle.tooling.model.idea.IdeaProject
@@ -53,12 +53,12 @@ class DependencyMetaDataCrossVersionSpec extends ToolingApiSpecification {
     }
 
     private void prepareBuild() {
-        def fakeRepo = dist.file("repo")
+        def fakeRepo = file("repo")
         new MavenFileRepository(fakeRepo).module("foo.bar", "coolLib", 2.0).publish()
 
-        dist.file("yetAnotherJar.jar").createFile()
+        file("yetAnotherJar.jar").createFile()
 
-        dist.file('build.gradle').text = """
+        file('build.gradle').text = """
 apply plugin: 'java'
 
 repositories {
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r12rc1/BuildModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r12rc1/BuildModelCrossVersionSpec.groovy
index e17d52a..bf87dac 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r12rc1/BuildModelCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r12rc1/BuildModelCrossVersionSpec.groovy
@@ -26,8 +26,7 @@ import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject
 @MinTargetGradleVersion("1.2-rc-1")
 class BuildModelCrossVersionSpec extends ToolingApiSpecification {
     def "can run tasks before building Eclipse model"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = '''
+        file('build.gradle').text = '''
 apply plugin: 'java'
 
 task setup << {
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r12rc1/ProjectOutcomesModuleCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r12rc1/ProjectOutcomesModuleCrossVersionSpec.groovy
index 7fe0af8..bdefe22 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r12rc1/ProjectOutcomesModuleCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r12rc1/ProjectOutcomesModuleCrossVersionSpec.groovy
@@ -27,7 +27,7 @@ import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes
 class ProjectOutcomesModuleCrossVersionSpec extends ToolingApiSpecification {
     def "modelContainsAllArchivesOnTheArchivesConfiguration"() {
         given:
-        dist.file('build.gradle') << '''
+        file('build.gradle') << '''
 			apply plugin: "java"
 
 			task zip(type: Zip) {
@@ -64,10 +64,10 @@ class ProjectOutcomesModuleCrossVersionSpec extends ToolingApiSpecification {
 
     def "modelContainsAllProjects"() {
         given:
-        dist.file('settings.gradle') << '''
+        file('settings.gradle') << '''
 include 'project1', 'project2'
 '''
-        dist.file('build.gradle') << '''
+        file('build.gradle') << '''
 apply plugin: 'java'
 '''
 
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r14/ToolingApiInitScriptCrossVersionIntegrationTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r14/ToolingApiInitScriptCrossVersionIntegrationTest.groovy
new file mode 100644
index 0000000..1e22319
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r14/ToolingApiInitScriptCrossVersionIntegrationTest.groovy
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.tooling.r14
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.tooling.GradleConnector
+import org.gradle.tooling.ProjectConnection
+import spock.lang.Issue
+
+/**
+ * Tests that init scripts are used from the _clients_ GRADLE_HOME, not the daemon server's.
+ */
+ at MinTargetGradleVersion('1.4')
+ at Issue("http://issues.gradle.org/browse/GRADLE-2408")
+class ToolingApiInitScriptCrossVersionIntegrationTest extends ToolingApiSpecification {
+
+    TestFile createDistribution(int i) {
+        def distro = file("distro$i")
+        distro.copyFrom(getTargetDist().getGradleHomeDir())
+        distro.file("bin", OperatingSystem.current().getScriptName("gradle")).permissions = 'rwx------'
+        distro.file("init.d/init.gradle") << """
+            gradle.allprojects {
+                task echo << { println "from distro $i" }
+            }
+        """
+        distro
+    }
+
+    String runWithInstallation(TestFile gradleHome) {
+        toolingApi.withConnector { GradleConnector it ->
+            it.useInstallation(new File(gradleHome.absolutePath))
+            it.useGradleUserHomeDir(temporaryFolder.file("user home"))
+            it.embedded(false)
+        }
+        withConnection { ProjectConnection connection ->
+            def baos = new ByteArrayOutputStream()
+            connection.newBuild().forTasks("echo").setStandardOutput(baos).run()
+            baos.toString()
+        }
+    }
+
+    def "init scripts from client distribution are used, not from the test"() {
+        given:
+        def distro1 = createDistribution(1)
+        def distro2 = createDistribution(2)
+
+        and:
+        buildFile << """
+            echo.doLast {
+                println "runtime gradle home: \${gradle.gradleHomeDir}"
+            }
+        """
+
+        when:
+        def distro1Output = runWithInstallation(distro1)
+
+        then:
+        distro1Output.contains "from distro 1"
+        distro1Output.contains "runtime gradle home: ${distro1.absolutePath}"
+
+        when:
+        def distro2Output = runWithInstallation(distro2)
+
+        then:
+        distro2Output.contains "from distro 2"
+        distro2Output.contains "runtime gradle home: ${distro1.absolutePath}"
+    }
+
+
+}
+
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DistributionFactoryTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DistributionFactoryTest.groovy
index 184dcc4..aec9852 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DistributionFactoryTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DistributionFactoryTest.groovy
@@ -17,17 +17,17 @@ package org.gradle.tooling.internal.consumer
 
 import org.gradle.logging.ProgressLogger
 import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.testing.internal.util.Network
 import org.gradle.util.DistributionLocator
 import org.gradle.util.GradleVersion
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
 import org.junit.Rule
 import spock.lang.IgnoreIf
 import spock.lang.Specification
 
 class DistributionFactoryTest extends Specification {
-    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final ProgressLoggerFactory progressLoggerFactory = Mock()
     final ProgressLogger progressLogger = Mock()
     final DistributionFactory factory = new DistributionFactory(tmpDir.file('userHome'))
@@ -41,7 +41,7 @@ class DistributionFactoryTest extends Specification {
         tmpDir.file('gradle/wrapper/gradle-wrapper.properties') << "distributionUrl=${zipFile.toURI()}"
 
         expect:
-        factory.getDefaultDistribution(tmpDir.dir, false).displayName == "Gradle distribution '${zipFile.toURI()}'"
+        factory.getDefaultDistribution(tmpDir.testDirectory, false).displayName == "Gradle distribution '${zipFile.toURI()}'"
     }
 
     def usesTheWrapperPropertiesToDetermineTheDefaultDistributionForASubprojectInAMultiProjectBuild() {
@@ -50,19 +50,19 @@ class DistributionFactoryTest extends Specification {
         tmpDir.file('gradle/wrapper/gradle-wrapper.properties') << "distributionUrl=${zipFile.toURI()}"
 
         expect:
-        factory.getDefaultDistribution(tmpDir.dir.createDir("child"), true).displayName == "Gradle distribution '${zipFile.toURI()}'"
+        factory.getDefaultDistribution(tmpDir.testDirectory.createDir("child"), true).displayName == "Gradle distribution '${zipFile.toURI()}'"
     }
 
     def usesTheCurrentVersionAsTheDefaultDistributionWhenNoWrapperPropertiesFilePresent() {
         def uri = new DistributionLocator().getDistributionFor(GradleVersion.current())
 
         expect:
-        factory.getDefaultDistribution(tmpDir.dir, false).displayName == "Gradle distribution '${uri}'"
+        factory.getDefaultDistribution(tmpDir.testDirectory, false).displayName == "Gradle distribution '${uri}'"
     }
 
     def createsADisplayNameForAnInstallation() {
         expect:
-        factory.getDistribution(tmpDir.dir).displayName == "Gradle installation '${tmpDir.dir}'"
+        factory.getDistribution(tmpDir.testDirectory).displayName == "Gradle installation '${tmpDir.testDirectory}'"
     }
 
     def usesContentsOfInstallationLibDirectoryAsImplementationClasspath() {
@@ -70,7 +70,7 @@ class DistributionFactoryTest extends Specification {
         def libB = tmpDir.createFile("lib/b.jar")
 
         expect:
-        def dist = factory.getDistribution(tmpDir.dir)
+        def dist = factory.getDistribution(tmpDir.testDirectory)
         dist.getToolingImplementationClasspath(progressLoggerFactory).asFiles as Set == [libA, libB] as Set
     }
 
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/SynchronizedLoggingTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/SynchronizedLoggingTest.groovy
index 8a74c85..edbb634 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/SynchronizedLoggingTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/SynchronizedLoggingTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.tooling.internal.consumer;
 
 
-import org.gradle.tests.fixtures.ConcurrentTestUtil
+import org.gradle.test.fixtures.ConcurrentTestUtil
 import spock.lang.Specification
 
 import java.util.concurrent.CopyOnWriteArraySet
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/DefaultToolingImplementationLoaderTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/DefaultToolingImplementationLoaderTest.groovy
index 5a01685..7b87549 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/DefaultToolingImplementationLoaderTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/DefaultToolingImplementationLoaderTest.groovy
@@ -18,6 +18,7 @@ package org.gradle.tooling.internal.consumer.loader
 import org.gradle.internal.classpath.DefaultClassPath
 import org.gradle.logging.ProgressLoggerFactory
 import org.gradle.messaging.actor.ActorFactory
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.tooling.UnsupportedVersionException
 import org.gradle.tooling.internal.consumer.Distribution
 import org.gradle.tooling.internal.consumer.connection.AdaptedConnection
@@ -27,13 +28,12 @@ import org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParamet
 import org.gradle.tooling.internal.protocol.*
 import org.gradle.util.ClasspathUtil
 import org.gradle.util.GradleVersion
-import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import org.slf4j.Logger
 import spock.lang.Specification
 
 class DefaultToolingImplementationLoaderTest extends Specification {
-    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     Distribution distribution = Mock()
     ProgressLoggerFactory loggerFactory = Mock()
 
@@ -69,7 +69,7 @@ class DefaultToolingImplementationLoaderTest extends Specification {
 
     private getToolingApiResourcesDir(Class implementation) {
         tmpDir.file("META-INF/services/org.gradle.tooling.internal.protocol.ConnectionVersion4") << implementation.name
-        return tmpDir.dir;
+        return tmpDir.testDirectory;
     }
 
     private getVersionResourcesDir() {
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/SynchronizedToolingImplementationLoaderTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/SynchronizedToolingImplementationLoaderTest.groovy
index 39932ef..1fec08d 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/SynchronizedToolingImplementationLoaderTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/SynchronizedToolingImplementationLoaderTest.groovy
@@ -16,16 +16,16 @@
 
 package org.gradle.tooling.internal.consumer.loader
 
-import org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters;
-
-import java.util.concurrent.locks.Lock
-import java.util.concurrent.locks.ReentrantLock
 import org.gradle.logging.ProgressLogger
 import org.gradle.logging.ProgressLoggerFactory
-import org.gradle.tests.fixtures.ConcurrentTestUtil
+import org.gradle.test.fixtures.ConcurrentTestUtil
 import org.gradle.tooling.internal.consumer.Distribution
+import org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters
 import spock.lang.Specification
 
+import java.util.concurrent.locks.Lock
+import java.util.concurrent.locks.ReentrantLock
+
 /**
  * by Szczepan Faber, created at: 12/15/11
  */
diff --git a/subprojects/tooling-api/tooling-api.gradle b/subprojects/tooling-api/tooling-api.gradle
index 94de843..e8e76a7 100644
--- a/subprojects/tooling-api/tooling-api.gradle
+++ b/subprojects/tooling-api/tooling-api.gradle
@@ -1,11 +1,12 @@
+import org.gradle.build.*
+import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact
 
 dependencies {
     groovy libraries.groovy
-
-    publishCompile project(':core')
-    publishCompile project(':baseServices')
-    publishCompile project(':messaging')
-    publishCompile project(':wrapper')
+    compile project(path: ':core', configuration: "publishCompileWithProjectJar")
+    compile project(':messaging')
+    compile project(':wrapper')
+    compile project(':baseServices')
     publishCompile libraries.slf4j_api
 
     // lots of integTest errors otherwise
@@ -16,22 +17,55 @@ useTestFixtures()
 
 integTestTasks.all {
     dependsOn({ rootProject.getTasksByName('publishLocalArchives', true) }, ':distributions:binZip')
+}
 
+daemonIntegTest {
+    enabled = false //tooling integ tests use daemon anyway, don't rerun
+}
+
+task jarjarJar(type: JarJarJar) {
+    appendix = "fat"
+    dependsOn jar
     doFirst {
-        systemProperties['org.gradle.integtest.toolingApiFromTestClasspath'] = 'true'
+        from(zipTree(jar.getArchivePath()))
+        (configurations.runtime - configurations.publishCompile).each {
+            from(zipTree(it))
+        }
     }
+    exclude "META-INF/**"
+    exclude "*classpath.properties"
+
+
+    rule('org.gradle.**', '@0')
+    rule('org.slf4j.**', '@0')
+
+    rule('org.**', 'org.gradle.jarjar. at 0')
+    rule('com.**', 'org.gradle.jarjar. at 0')
+    rule('net.**', 'org.gradle.jarjar. at 0')
+
+    keep('org.gradle.tooling.**')
 }
 
-daemonIntegTest {
-    enabled = false //tooling integ tests use daemon anyway, don't rerun
+sourceJar{
+    configurations.compile.allDependencies.withType(ProjectDependency).each {
+        from it.dependencyProject.sourceSets.main.groovy.srcDirs
+        from it.dependencyProject.sourceSets.main.java.srcDirs
+    }
+}
+
+artifacts {
+    publishCompileWithProjectJar jarjarJar
+    publishRuntime file: jarjarJar.getArchivePath(), name: archivesBaseName, type: 'jar', builtBy: jarjarJar
 }
 
+configurations.publishRuntime { artifacts.removeAll { it instanceof ArchivePublishArtifact && it.archiveTask == jar } }
+
 eclipse {
-	classpath {
-    	file.whenMerged { classpath ->
-        	 //**TODO
-        	classpath.entries.removeAll { it.path.contains('src/test/groovy') }
-        	classpath.entries.removeAll { it.path.contains('src/integTest/groovy') }
+    classpath {
+        file.whenMerged { classpath ->
+            //**TODO
+            classpath.entries.removeAll { it.path.contains('src/test/groovy') }
+            classpath.entries.removeAll { it.path.contains('src/integTest/groovy') }
         }
     }
 }
diff --git a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/FavoritesIntegrationTest.java b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/FavoritesIntegrationTest.java
index 3828384..54cb773 100644
--- a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/FavoritesIntegrationTest.java
+++ b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/FavoritesIntegrationTest.java
@@ -23,7 +23,7 @@ import org.gradle.foundation.TaskView;
 import org.gradle.foundation.TestUtility;
 import org.gradle.gradleplugin.foundation.favorites.FavoriteTask;
 import org.gradle.gradleplugin.foundation.favorites.FavoritesEditor;
-import org.gradle.util.TemporaryFolder;
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.Assert;
 import org.junit.Before;
@@ -43,7 +43,7 @@ import java.util.List;
  */
 public class FavoritesIntegrationTest {
     @Rule
-    public final TemporaryFolder tempDir = new TemporaryFolder();
+    public final TestNameTestDirectoryProvider tempDir = new TestNameTestDirectoryProvider();
     private BuildInformation buildInformation;
 
     private ProjectView myRootProject;
diff --git a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy
index 0c6009b..9bb20c6 100644
--- a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy
+++ b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy
@@ -15,31 +15,31 @@
 */
 package org.gradle.integtests
 
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.locks.Condition
-import java.util.concurrent.locks.Lock
-import java.util.concurrent.locks.ReentrantLock
 import org.gradle.foundation.TestUtility
 import org.gradle.foundation.ipc.gradle.ExecuteGradleCommandServerProtocol
+import org.gradle.foundation.output.FileLink
+import org.gradle.foundation.output.FileLinkDefinitionLord
+import org.gradle.foundation.output.LiveOutputParser
 import org.gradle.gradleplugin.foundation.GradlePluginLord
 import org.gradle.gradleplugin.foundation.runner.GradleRunner
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.Sample
+import org.gradle.logging.ShowStacktrace
 import org.junit.Assert
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
-import org.gradle.foundation.output.LiveOutputParser
-import org.gradle.foundation.output.FileLinkDefinitionLord
-import org.gradle.foundation.output.FileLink
-import org.gradle.logging.ShowStacktrace
+
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.locks.Condition
+import java.util.concurrent.locks.Lock
+import java.util.concurrent.locks.ReentrantLock
 
 /**
 This tests the that live output is gathered while executing a task.
 @author mhunsicker
 */
-class LiveOutputIntegrationTest {
+class LiveOutputIntegrationTest extends AbstractIntegrationTest {
 
     static final String JAVA_PROJECT_NAME = 'javaproject'
     static final String SHARED_NAME = 'shared'
@@ -50,8 +50,6 @@ class LiveOutputIntegrationTest {
 
     private File javaprojectDir
 
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
     @Rule public final Sample sample = new Sample('java/quickstart')
 
     @Before
@@ -74,8 +72,8 @@ that's likely to change over time. This version executes the command via GradleP
 
         GradlePluginLord gradlePluginLord = new GradlePluginLord();
         gradlePluginLord.setCurrentDirectory(multiProjectDirectory);
-        gradlePluginLord.setGradleHomeDirectory(dist.gradleHomeDir);
-        gradlePluginLord.addCommandLineArgumentAlteringListener(new ExtraTestCommandLineOptionsListener(dist.userHomeDir))
+        gradlePluginLord.setGradleHomeDirectory(distribution.gradleHomeDir);
+        gradlePluginLord.addCommandLineArgumentAlteringListener(new ExtraTestCommandLineOptionsListener(executer.gradleUserHomeDir))
 
         gradlePluginLord.startExecutionQueue(); //for tests, we'll need to explicitly start the execution queue (unless we do a refresh via the TestUtility).
 
@@ -99,12 +97,12 @@ that's likely to change over time. This version executes the command via GradleR
         File multiProjectDirectory = sample.getDir();
         Assert.assertTrue(multiProjectDirectory.exists()); //make sure things are setup the way we expect
 
-        GradleRunner gradleRunner = new GradleRunner( multiProjectDirectory, dist.gradleHomeDir, null );
+        GradleRunner gradleRunner = new GradleRunner( multiProjectDirectory, distribution.gradleHomeDir, null );
 
         TestExecutionInteraction executionInteraction = new TestExecutionInteraction();
 
         //execute a command. We don't really care what the command is, just something that generates output
-        def cl = new ExtraTestCommandLineOptionsListener(dist.userHomeDir).getAdditionalCommandLineArguments('') + ' tasks'
+        def cl = new ExtraTestCommandLineOptionsListener(executer.gradleUserHomeDir).getAdditionalCommandLineArguments('') + ' tasks'
         gradleRunner.executeCommand(cl, org.gradle.api.logging.LogLevel.LIFECYCLE,
                                             ShowStacktrace.INTERNAL_EXCEPTIONS,
                                             executionInteraction);
diff --git a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy
index 141e7ec..f0c3825 100644
--- a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy
+++ b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy
@@ -15,13 +15,11 @@
  */
 package org.gradle.integtests
 
-import java.util.concurrent.TimeUnit
 import org.gradle.foundation.ProjectView
 import org.gradle.foundation.TaskView
 import org.gradle.foundation.TestUtility
 import org.gradle.gradleplugin.foundation.GradlePluginLord
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.openapi.external.foundation.ProjectVersion1
 import org.gradle.openapi.wrappers.foundation.GradleInterfaceWrapperVersion1
@@ -30,11 +28,13 @@ import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 
+import java.util.concurrent.TimeUnit
+
 /**
  This tests the multiproject sample with the GradleView mechanism.
  @author mhunsicker
  */
-class MultiprojectProjectAndTaskListIntegrationTest {
+class MultiprojectProjectAndTaskListIntegrationTest extends AbstractIntegrationTest {
 
     static final String JAVA_PROJECT_NAME = 'javaproject'
     static final String SHARED_NAME = 'shared'
@@ -43,16 +43,14 @@ class MultiprojectProjectAndTaskListIntegrationTest {
     static final String SERVICES_NAME = 'services'
     static final String WEBAPP_PATH = "$SERVICES_NAME/$WEBAPP_NAME" as String
 
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
     @Rule public final Sample sample = new Sample('java/multiproject')
     GradlePluginLord gradlePluginLord = new GradlePluginLord()
 
     @Before
     void setUp() {
         gradlePluginLord.setCurrentDirectory(sample.dir);
-        gradlePluginLord.setGradleHomeDirectory(dist.gradleHomeDir);
-        gradlePluginLord.addCommandLineArgumentAlteringListener(new ExtraTestCommandLineOptionsListener(dist.userHomeDir))
+        gradlePluginLord.setGradleHomeDirectory(distribution.gradleHomeDir);
+        gradlePluginLord.addCommandLineArgumentAlteringListener(new ExtraTestCommandLineOptionsListener(executer.gradleUserHomeDir))
     }
 
     /*
diff --git a/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperCrossVersionIntegrationTest.groovy b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperCrossVersionIntegrationTest.groovy
index e493c13..126fff8 100644
--- a/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperCrossVersionIntegrationTest.groovy
+++ b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperCrossVersionIntegrationTest.groovy
@@ -15,8 +15,8 @@
  */
 package org.gradle.integtests
 
-import org.gradle.integtests.fixtures.BasicGradleDistribution
 import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
+import org.gradle.integtests.fixtures.executer.GradleDistribution
 
 class WrapperCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
     public void canUseWrapperFromPreviousVersionToRunCurrentVersion() {
@@ -29,9 +29,9 @@ class WrapperCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
         checkWrapperWorksWith(current, previous)
     }
 
-    void checkWrapperWorksWith(BasicGradleDistribution wrapperGenVersion, BasicGradleDistribution executionVersion) {
+    void checkWrapperWorksWith(GradleDistribution wrapperGenVersion, GradleDistribution executionVersion) {
         if (!wrapperGenVersion.wrapperCanExecute(executionVersion.version)) {
-            println "skipping $wrapperGenVersion as its wrapper cannot execute version ${executionVersion.version}"
+            println "skipping $wrapperGenVersion as its wrapper cannot execute version ${executionVersion.version.version}"
             return
         }
 
@@ -40,7 +40,7 @@ class WrapperCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
         buildFile << """
 
 task wrapper(type: Wrapper) {
-    gradleVersion = '$executionVersion.version'
+    gradleVersion = '$executionVersion.version.version'
 }
 
 //(SF) not sure if we want to keep coverage for old 'urlRoot' that was already removed
@@ -60,8 +60,8 @@ task hello {
 }
 """
         version(wrapperGenVersion).withTasks('wrapper').run()
-        def result = version(wrapperGenVersion).usingExecutable('gradlew').withTasks('hello').run()
-        assert result.output.contains("hello from $executionVersion.version")
+        def result = version(wrapperGenVersion).usingExecutable('gradlew').withDeprecationChecksDisabled().withTasks('hello').run()
+        assert result.output.contains("hello from $executionVersion.version.version")
     }
 }
 
diff --git a/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy
index 1552469..b142b66 100644
--- a/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy
+++ b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy
@@ -17,9 +17,9 @@
 package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.ExecutionFailure
-import org.gradle.integtests.fixtures.ExecutionResult
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.executer.ExecutionFailure
+import org.gradle.integtests.fixtures.executer.ExecutionResult
+import org.gradle.integtests.fixtures.executer.GradleExecuter
 import org.gradle.test.fixtures.server.http.HttpServer
 import org.gradle.test.fixtures.server.http.TestProxyServer
 import org.gradle.util.GradleVersion
@@ -45,8 +45,8 @@ class WrapperProjectIntegrationTest extends AbstractIntegrationSpec {
         server.expectUserAgent(matchesNameAndVersion("gradlew", GradleVersion.current().getVersion()))
     }
 
-    GradleDistributionExecuter getWrapperExecuter() {
-        executer.usingExecutable('gradlew').inDirectory(testDir)
+    GradleExecuter getWrapperExecuter() {
+        executer.usingExecutable('gradlew').inDirectory(testDirectory)
     }
 
     private prepareWrapper(String baseUrl) {
diff --git a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/DownloadTest.groovy b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/DownloadTest.groovy
index 4b3b90e..57ba024 100644
--- a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/DownloadTest.groovy
+++ b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/DownloadTest.groovy
@@ -16,7 +16,7 @@
 
 package org.gradle.wrapper
 
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -34,11 +34,11 @@ class DownloadTest {
     URI sourceRoot
     File remoteFile
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
     @Before public void setUp() {
         download = new Download("gradlew", "aVersion")
-        testDir = tmpDir.dir
+        testDir = tmpDir.testDirectory
         rootDir = new File(testDir, 'root')
         downloadFile = new File(rootDir, 'file')
         (remoteFile = new File(testDir, 'remoteFile')).write('sometext')
diff --git a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/InstallTest.groovy b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/InstallTest.groovy
index e437c0c..55f5d38 100644
--- a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/InstallTest.groovy
+++ b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/InstallTest.groovy
@@ -16,13 +16,14 @@
 
 package org.gradle.wrapper
 
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
-import static org.junit.Assert.assertEquals
 import spock.lang.Specification
-import org.gradle.util.TestFile
+
+import static org.junit.Assert.assertEquals
 
 /**
  * @author Hans Dockter
@@ -43,11 +44,11 @@ class InstallTest extends Specification {
     PathAssembler pathAssembler = Mock()
     PathAssembler.LocalDistribution localDistribution = Mock()
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
+    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
 
     @Before public void setup() {
         downloadCalled = false
-        testDir = tmpDir.dir
+        testDir = tmpDir.testDirectory
         configuration.zipBase = PathAssembler.PROJECT_STRING
         configuration.zipPath = 'someZipPath'
         configuration.distributionBase = PathAssembler.GRADLE_USER_HOME_STRING
diff --git a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/SystemPropertiesHandlerTest.groovy b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/SystemPropertiesHandlerTest.groovy
index 93e7a8e..bb7cf9e 100644
--- a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/SystemPropertiesHandlerTest.groovy
+++ b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/SystemPropertiesHandlerTest.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.wrapper
 
-import org.gradle.util.TemporaryFolder
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -24,7 +24,7 @@ import spock.lang.Specification
  */
 class SystemPropertiesHandlerTest extends Specification {
     @Rule
-    TemporaryFolder tmpDir = new TemporaryFolder()
+    TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
 
     def parsesPropertiesFile() {
         File propFile = tmpDir.file('props')
diff --git a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/WrapperExecutorTest.groovy b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/WrapperExecutorTest.groovy
index ce088b5..b7dad29 100644
--- a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/WrapperExecutorTest.groovy
+++ b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/WrapperExecutorTest.groovy
@@ -15,14 +15,14 @@
  */
 package org.gradle.wrapper
 
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
 class WrapperExecutorTest extends Specification {
     @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     final Install install = Mock()
     final BootstrapMainStarter start = Mock()
     TestFile projectDir;
@@ -30,7 +30,7 @@ class WrapperExecutorTest extends Specification {
     Properties properties = new Properties()
 
     def setup() {
-        projectDir = tmpDir.dir
+        projectDir = tmpDir.testDirectory
         propertiesFile = tmpDir.file('gradle/wrapper/gradle-wrapper.properties')
 
         properties.distributionUrl = 'http://server/test/gradle.zip'
diff --git a/version.txt b/version.txt
new file mode 100644
index 0000000..840ca8c
--- /dev/null
+++ b/version.txt
@@ -0,0 +1 @@
+1.4
\ No newline at end of file

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/gradle-1.12.git



More information about the pkg-java-commits mailing list