Merge branch 'master' into 2.0.x

This commit is contained in:
Marcin Grzejszczak
2017-07-03 13:03:45 +02:00
8 changed files with 198 additions and 15 deletions

View File

@@ -124,6 +124,7 @@ contracts {
}
contractsPath = ''
contractsWorkOffline = false
cacheDownloadedContracts = true
}
tasks.create(type: Jar, name: 'verifierStubsJar', dependsOn: 'generateClientStubs') {
@@ -443,8 +444,9 @@ If you want to download your contract definitions from a Maven repository you ca
- **contractDependency** - the contract dependency that contains all the packaged contracts
- **contractsPath** - path to concrete contracts in the JAR with packaged contracts. Defaults to `groupid/artifactid` where `gropuid` is slash separated.
- **contractsWorkOffline** - if the dependencies should be downloaded or local Maven only should be reused
For complete information take a look at https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract-maven-plugin/plugin-info.html[Plugin Documentation]
- **cacheDownloadedContracts** - if you want to reuse download JARs that contain contract definitions.
We cache only non-snapshot, explicitly provided versions (e.g. `+` or `1.0.0.BUILD-SNAPSHOT` won't get cached).
By default this feature is turned on.
====== Single base class for all tests

View File

@@ -86,6 +86,10 @@ public class StubConfiguration {
return StringUtils.hasText(this.groupId) && StringUtils.hasText(this.artifactId);
}
/**
* Returns a colon separated representation of the stub configuration
* (e.g. groupid:artifactid:version:classifier)
*/
public String toColonSeparatedDependencyNotation() {
if (!isDefined()) {
return "";
@@ -102,6 +106,13 @@ public class StubConfiguration {
return StringUtils.hasText(value) ? value : "";
}
/**
* Checks if ivy notation matches group and artifact ids
*
* @param ivyNotationAsString - e.g. group:artifact:version:classifier
* @return {@code true} if artifact id matches and there's no group id. Or if
* both group id and artifact id are present and matching
*/
public boolean groupIdAndArtifactMatches(String ivyNotationAsString) {
String[] parts = ivyNotationFrom(ivyNotationAsString);
String groupId = parts[0];
@@ -112,6 +123,14 @@ public class StubConfiguration {
return this.groupId.equals(groupId) && this.artifactId.equals(artifactId);
}
/**
* Returns {@code true} for a snapshot or a LATEST (+) version
*/
public boolean isVersionChanging() {
return DEFAULT_VERSION.equals(this.version) ||
this.version.toLowerCase().contains("snapshot");
}
public String getGroupId() {
return this.groupId;
}

View File

@@ -1,18 +1,20 @@
package org.springframework.cloud.contract.stubrunner
import io.specto.hoverfly.junit.HoverflyRule
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.RepositorySystemSession
import org.junit.Rule
import org.springframework.util.ResourceUtils
import spock.lang.IgnoreIf
import spock.lang.Specification
import spock.util.environment.RestoreSystemProperties
class AetherStubDownloaderSpec extends Specification {
@Rule
HoverflyRule hoverflyRule = HoverflyRule.inSimulationMode("simulation.json")
// CI tools sometimes can't reach the `test.jfrog.io` address
@IgnoreIf({ Boolean.valueOf(env['CI']) })
def 'Should be able to download from a repository using username and password authentication'() {
given:
StubRunnerOptions stubRunnerOptions = new StubRunnerOptionsBuilder()

View File

@@ -45,4 +45,19 @@ class StubConfigurationSpec extends Specification {
}
@Unroll
def 'should resolve [#ivy] as a changing version [#result]'() {
given:
StubConfiguration stubConfiguration = new StubConfiguration(ivy)
expect:
result == stubConfiguration.isVersionChanging()
where:
ivy || result
'group:artifact:1.0.0.RELEASE:classifier' || false
'group:artifact:1.0.0.BUILD-SNAPSHOT:' || true
'group:artifact:1.0.0.SNAPSHOT' || true
'group:artifact:+:' || true
}
}

View File

@@ -141,6 +141,12 @@ class ContractVerifierExtension {
*/
boolean excludeBuildFolders
/**
* If set to true then will cache the folder where non snapshot contract artifacts
* got downloaded.
*/
boolean cacheDownloadedContracts = true
void contractDependency(@DelegatesTo(Dependency) Closure closure) {
closure.delegate = contractDependency
closure.call()

View File

@@ -1,5 +1,6 @@
package org.springframework.cloud.contract.verifier.plugin
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
@@ -7,6 +8,7 @@ import org.springframework.cloud.contract.verifier.config.ContractVerifierConfig
* @author Marcin Grzejszczak
*/
@PackageScope
@CompileStatic
class ExtensionToProperties {
protected static ContractVerifierConfigProperties fromExtension(ContractVerifierExtension extension) {

View File

@@ -1,11 +1,14 @@
package org.springframework.cloud.contract.verifier.plugin
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.gradle.api.Project
import org.gradle.api.logging.Logger
import org.springframework.cloud.contract.stubrunner.AetherStubDownloader
import org.springframework.cloud.contract.stubrunner.ContractDownloader
import org.springframework.cloud.contract.stubrunner.StubConfiguration
import org.springframework.cloud.contract.stubrunner.StubDownloader
import org.springframework.cloud.contract.stubrunner.StubDownloaderBuilderProvider
import org.springframework.cloud.contract.stubrunner.StubRunnerOptionsBuilder
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import org.springframework.util.StringUtils
@@ -15,13 +18,14 @@ import java.util.concurrent.ConcurrentHashMap
* @author Marcin Grzejszczak
*/
@PackageScope
@CompileStatic
class GradleContractsDownloader {
private static final String LATEST_VERSION = '+'
private final Project project
private final Logger log
private static final Map<StubConfiguration, File> downloadedContract = new ConcurrentHashMap<>()
protected static final Map<StubConfiguration, File> downloadedContract = new ConcurrentHashMap<>()
GradleContractsDownloader(Project project, Logger log) {
this.project = project
@@ -31,24 +35,29 @@ class GradleContractsDownloader {
File downloadAndUnpackContractsIfRequired(ContractVerifierExtension extension,
ContractVerifierConfigProperties config) {
File defaultContractsDir = extension.contractsDslDir
this.log.info("Project has group id [${this.project.group}], artifact id [${this.project.name}]")
this.log.info("Project has group id [{}], artifact id [{}]", this.project.group, this.project.name)
// download contracts, unzip them and pass as output directory
if (shouldDownloadContracts(extension)) {
this.log.info("For project [${this.project.name}] Download dependency is provided - will download contract jars")
this.log.info("Contract dependency [{}]", extension.contractDependency)
StubConfiguration configuration = stubConfiguration(extension.contractDependency)
this.log.info("Got the following contract dependency to download [{}]", configuration)
File cachedFolder = downloadedContract.get(configuration)
if (cachedFolder) {
this.log.info("For project [${this.project.name}] Returning the cached location of the contracts")
contractDownloader(extension, configuration).updatePropertiesWithInclusion(cachedFolder, config)
return cachedFolder
this.log.info("The contract dependency is a changing one [{}] and cache download switch is set to [{}]",
configuration.isVersionChanging(), extension.cacheDownloadedContracts)
if (!configuration.isVersionChanging() && extension.cacheDownloadedContracts) {
this.log.info("Resolved a non changing version - will try to return the folder from a cache")
File cachedFolder = downloadedContract.get(configuration)
if (cachedFolder) {
this.log.info("For project [{}] returning the cached location of the contracts", this.project.name)
contractDownloader(extension, configuration).updatePropertiesWithInclusion(cachedFolder, config)
return cachedFolder
}
}
File downloadedContracts = contractDownloader(extension, configuration).unpackedDownloadedContracts(config)
downloadedContract.put(configuration, downloadedContracts)
return downloadedContracts
}
this.log.info("For project [${this.project.name}] will use contracts provided in the folder [" + defaultContractsDir + "]")
this.log.info("For project [{}] will use contracts provided in the folder [{}]", this.project.name, defaultContractsDir)
return defaultContractsDir
}
@@ -58,13 +67,14 @@ class GradleContractsDownloader {
StringUtils.hasText(extension.contractDependency.stringNotation))
}
private ContractDownloader contractDownloader(ContractVerifierExtension extension, StubConfiguration configuration) {
protected ContractDownloader contractDownloader(ContractVerifierExtension extension, StubConfiguration configuration) {
return new ContractDownloader(stubDownloader(extension), configuration,
extension.contractsPath, this.project.group as String, this.project.name)
}
private AetherStubDownloader stubDownloader(ContractVerifierExtension extension) {
return new AetherStubDownloader(
protected StubDownloader stubDownloader(ContractVerifierExtension extension) {
StubDownloaderBuilderProvider provider = new StubDownloaderBuilderProvider()
return provider.getOrDefaultDownloader(
new StubRunnerOptionsBuilder()
.withStubRepositoryRoot(extension.contractsRepositoryUrl)
.withWorkOffline(extension.contractsWorkOffline)

View File

@@ -1,6 +1,11 @@
package org.springframework.cloud.contract.verifier.plugin
import org.gradle.api.Project
import org.gradle.api.logging.Logger
import org.springframework.cloud.contract.stubrunner.AetherStubDownloader
import org.springframework.cloud.contract.stubrunner.ContractDownloader
import org.springframework.cloud.contract.stubrunner.StubConfiguration
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import spock.lang.Specification
/**
@@ -8,6 +13,9 @@ import spock.lang.Specification
*/
class GradleContractsDownloaderSpec extends Specification {
Project project = Stub(Project)
Logger logger = Stub(Logger)
def "should parse dependency via string notation"() {
given:
String stringNotation = "com.example:foo:1.0.0:stubs"
@@ -37,6 +45,7 @@ class GradleContractsDownloaderSpec extends Specification {
stubConfig.version == "1.0.0"
stubConfig.classifier == "stubs"
}
def "should parse dependency via string notation with methods"() {
given:
String stringNotation = "com.example:foo:1.0.0:stubs"
@@ -66,4 +75,122 @@ class GradleContractsDownloaderSpec extends Specification {
stubConfig.version == "1.0.0"
stubConfig.classifier == "stubs"
}
def "should pick dependency from cache for a non snapshot contract dependency"() {
given:
ContractVerifierExtension ext = new ContractVerifierExtension()
ext.with {
contractDependency {
groupId("com.example")
artifactId("foo")
version("1.0.0")
classifier("stubs")
}
contractsRepositoryUrl = "foo"
}
and:
final AetherStubDownloader downloader = Mock(AetherStubDownloader)
final ContractDownloader contractDownloader = Mock(ContractDownloader)
and:
def gradleDownloader = stubbedContractDownloader(downloader, contractDownloader)
and:
StubConfiguration expectedStubConfig = new StubConfiguration("com.example:foo:1.0.0:stubs")
File expectedFileFromCache = new File("foo/bar")
GradleContractsDownloader.downloadedContract.put(expectedStubConfig, expectedFileFromCache)
when:
File file = gradleDownloader.downloadAndUnpackContractsIfRequired(ext, new ContractVerifierConfigProperties())
then:
file == expectedFileFromCache
}
def "should not pick dependency from cache for a non snapshot contract dependency with cache switch off"() {
given:
ContractVerifierExtension ext = new ContractVerifierExtension()
ext.with {
contractDependency {
groupId("com.example")
artifactId("foo")
version("1.0.0")
classifier("stubs")
}
contractsRepositoryUrl = "foo"
cacheDownloadedContracts = false
}
and:
final AetherStubDownloader downloader = Mock(AetherStubDownloader)
final ContractDownloader contractDownloader = Mock(ContractDownloader)
and:
def gradleDownloader = stubbedContractDownloader(downloader, contractDownloader)
and:
StubConfiguration expectedStubConfig = new StubConfiguration("com.example:foo:1.0.0:stubs")
File expectedFileFromCache = new File("foo/bar")
GradleContractsDownloader.downloadedContract.put(expectedStubConfig, expectedFileFromCache)
and:
File expectedFileNotFromCache = new File("foo/bar/baz")
contractDownloader.unpackedDownloadedContracts(_) >> expectedFileNotFromCache
when:
File file = gradleDownloader.downloadAndUnpackContractsIfRequired(ext, new ContractVerifierConfigProperties())
then:
file == expectedFileNotFromCache
}
def "should not pick dependency from cache for snapshot contract dependency"() {
given:
ContractVerifierExtension ext = new ContractVerifierExtension()
ext.with {
contractDependency {
groupId("com.example")
artifactId("foo")
version("1.0.0.BUILD-SNAPSHOT")
classifier("stubs")
}
contractsRepositoryUrl = "foo"
}
and:
final AetherStubDownloader downloader = Mock(AetherStubDownloader)
final ContractDownloader contractDownloader = Mock(ContractDownloader)
File expectedFileNotFromCache = new File("foo/bar/baz")
contractDownloader.unpackedDownloadedContracts(_) >> expectedFileNotFromCache
and:
def gradleDownloader = stubbedContractDownloader(downloader, contractDownloader)
when:
File file = gradleDownloader.downloadAndUnpackContractsIfRequired(ext, new ContractVerifierConfigProperties())
then:
file == expectedFileNotFromCache
}
private GradleContractsDownloader stubbedContractDownloader(downloader, contractDownloader) {
new GradleContractsDownloader(project, logger) {
@Override
protected AetherStubDownloader stubDownloader(ContractVerifierExtension extension) {
return downloader
}
@Override
protected ContractDownloader contractDownloader(ContractVerifierExtension extension, StubConfiguration configuration) {
return contractDownloader
}
}
}
def "should pick contract directory location from extension"() {
given:
ContractVerifierExtension ext = new ContractVerifierExtension()
ext.with {
contractsDslDir = new File("/foo/bar/baz")
}
and:
final AetherStubDownloader downloader = Mock(AetherStubDownloader)
final ContractDownloader contractDownloader = Mock(ContractDownloader)
and:
def gradleDownloader = stubbedContractDownloader(downloader, contractDownloader)
and:
StubConfiguration expectedStubConfig = new StubConfiguration("com.example:foo:1.0.0:stubs")
GradleContractsDownloader.downloadedContract.put(expectedStubConfig, new File("foo/bar"))
when:
File file = gradleDownloader.downloadAndUnpackContractsIfRequired(ext, new ContractVerifierConfigProperties())
then:
file == new File("/foo/bar/baz")
}
}