Merge branch 'master' into feature/kotlin-contract-support

This commit is contained in:
Marcin Grzejszczak
2019-08-14 10:49:35 +02:00
43 changed files with 1261 additions and 1145 deletions

View File

@@ -624,7 +624,7 @@ You can check out the https://github.com/spring-cloud/spring-cloud-pipelines[Spr
project for more information.
[[features-stub-runner-boot-server]]
===== Stub Runner Server
==== Stub Runner Server
To use the Stub Runner Server, add the following dependency:
@@ -640,7 +640,7 @@ Then annotate a class with `@EnableStubRunnerServer`, build a fat jar, and it is
For the properties, see the <<features-stub-runner-rule-spring,Stub Runner Spring>> section.
[[features-stub-runner-boot-how-fat-jar]]
===== Stub Runner Server Fat Jar
==== Stub Runner Server Fat Jar
You can download a standalone JAR from Maven (for example, for version 2.0.1.RELEASE)
by running the following commands:
@@ -654,7 +654,7 @@ $ java -jar stub-runner.jar --stubrunner.ids=... --stubrunner.repositoryRoot=...
====
[[features-stub-runner-boot-how-cli]]
===== Spring Cloud CLI
==== Spring Cloud CLI
Starting from the `1.4.0.RELEASE` version of the https://cloud.spring.io/spring-cloud-cli[Spring Cloud CLI]
project, you can start Stub Runner Boot by running `spring cloud stubrunner`.
@@ -905,7 +905,7 @@ stubsMode = StubRunnerProperties.StubsMode.REMOTE,
Contracts and stubs may be stored in a location, where each producer has its own, dedicated folder for contracts and stub mappings. Under that folder each consumer can have its own setup. To make Stub Runner find the dedicated folder from the provided ids one can pass a property `stubs.find-producer=true` or a system property `stubrunner.stubs.find-producer=true` .
[source,bash,indent=0]
====
----
└── com.example <1>
├── some-artifact-id <2>
│   └── 0.0.1
@@ -919,7 +919,7 @@ Contracts and stubs may be stored in a location, where each producer has its own
└── mappings
└── shouldReturnStuffForOtherArtifactId.json
====
----
<1> group id of the consumers
<2> consumer with artifact id [some-artifact-id]
<3> contracts for the consumer with artifact id [some-artifact-id]

View File

@@ -70,14 +70,52 @@ public class ContractDownloader {
* pattern
* @return location of the unpacked downloaded stubs
*/
// Use unpackAndDownloadContracts() and createNewInclusionProperties() instead
@Deprecated
public File unpackedDownloadedContracts(ContractVerifierConfigProperties config) {
File contractsDirectory = unpackAndDownloadContracts();
updatePropertiesWithInclusion(contractsDirectory, config);
return contractsDirectory;
}
// Use createNewInclusionProperties() instead
@Deprecated
public ContractVerifierConfigProperties updatePropertiesWithInclusion(
File contractsDirectory, ContractVerifierConfigProperties config) {
final InclusionProperties newInclusionProperties = createNewInclusionProperties(
contractsDirectory);
config.setIncludedContracts(newInclusionProperties.getIncludedContracts());
config.setIncludedRootFolderAntPattern(
newInclusionProperties.getIncludedRootFolderAntPattern());
return config;
}
/**
* Downloads JAR containing all the contracts. The JAR with the contracts contains all
* the contracts for all the projects. We're interested only in its subset.
* @return location of the unpacked downloaded stubs
*/
public File unpackAndDownloadContracts() {
if (log.isDebugEnabled()) {
log.debug("Will download contracts for [" + this.contractsJarStubConfiguration
+ "]");
}
Map.Entry<StubConfiguration, File> unpackedContractStubs = this.stubDownloader
.downloadAndUnpackStubJar(this.contractsJarStubConfiguration);
if (unpackedContractStubs == null) {
throw new IllegalStateException("The contracts failed to be downloaded!");
}
return unpackedContractStubs.getValue();
}
/**
* After JAR with all the contracts is downloaded and unpacked - we need to get new
* inclusion pattern for those contracts. The JAR with the contracts contains all the
* contracts for all the projects. We're interested only in its subset.
* @param contractsDirectory - location of the unpacked downloaded stubs.
* @return new inclusion properties, calculated for those downloaded contracts.
*/
public InclusionProperties createNewInclusionProperties(File contractsDirectory) {
String pattern;
String includedAntPattern;
if (StringUtils.hasText(this.contractsPath)) {
@@ -107,9 +145,7 @@ public class ContractDownloader {
}
log.info("Pattern to pick contracts equals [" + pattern + "]");
log.info("Ant Pattern to pick files equals [" + includedAntPattern + "]");
config.setIncludedContracts(pattern);
config.setIncludedRootFolderAntPattern(includedAntPattern);
return config;
return new InclusionProperties(pattern, includedAntPattern);
}
private File contractsSubDirIfPresent(File contractsDirectory) {
@@ -162,19 +198,6 @@ public class ContractDownloader {
+ "**/";
}
private File unpackAndDownloadContracts() {
if (log.isDebugEnabled()) {
log.debug("Will download contracts for [" + this.contractsJarStubConfiguration
+ "]");
}
Map.Entry<StubConfiguration, File> unpackedContractStubs = this.stubDownloader
.downloadAndUnpackStubJar(this.contractsJarStubConfiguration);
if (unpackedContractStubs == null) {
throw new IllegalStateException("The contracts failed to be downloaded!");
}
return unpackedContractStubs.getValue();
}
private String groupArtifactToPattern(File contractsDirectory) {
return ("^" + contractsDirectory.getAbsolutePath() + "(" + File.separator + ")?"
+ ".*" + slashSeparatedGroupId() + File.separator + this.projectArtifactId
@@ -189,4 +212,36 @@ public class ContractDownloader {
return this.projectGroupId.replace(".", File.separator);
}
/**
* Holder for updated inclusion properties, which are calculated after jar with
* contracts was downloaded / unpacked.
*/
public static class InclusionProperties {
/**
* @see ContractVerifierConfigProperties.includedContracts
*/
private final String includedContracts;
/**
* @see ContractVerifierConfigProperties.includedRootFolderAntPattern
*/
private final String includedRootFolderAntPattern;
InclusionProperties(final String includedContracts,
final String includedRootFolderAntPattern) {
this.includedContracts = includedContracts;
this.includedRootFolderAntPattern = includedRootFolderAntPattern;
}
public String getIncludedContracts() {
return includedContracts;
}
public String getIncludedRootFolderAntPattern() {
return includedRootFolderAntPattern;
}
}
}

View File

@@ -31,7 +31,6 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties;
import org.springframework.cloud.contract.verifier.converter.RecursiveFilesConverter;
import org.springframework.cloud.contract.verifier.converter.StubGenerator;
import org.springframework.cloud.contract.verifier.converter.StubGeneratorProvider;
@@ -149,12 +148,10 @@ class StubRunnerFactory {
private void generateNewMappings(Path path) {
File unpackedLocation = path.toFile();
ContractVerifierConfigProperties configProperties = new ContractVerifierConfigProperties();
configProperties
.setContractsDslDir(subfolderIfPresent(unpackedLocation, "contracts"));
configProperties
.setStubsOutputDir(subfolderIfPresent(unpackedLocation, "mappings"));
RecursiveFilesConverter converter = new RecursiveFilesConverter(configProperties);
RecursiveFilesConverter converter = new RecursiveFilesConverter(
subfolderIfPresent(unpackedLocation, "mappings"),
subfolderIfPresent(unpackedLocation, "contracts"), new ArrayList<>(),
".*", false);
converter.processFiles();
}

View File

@@ -18,8 +18,6 @@ package org.springframework.cloud.contract.stubrunner
import spock.lang.Specification
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
/**
* @author Marcin Grzejszczak
*/
@@ -35,16 +33,13 @@ class ContractDownloaderSpec extends Specification {
String contractPath = File.separator + ['a', 'b', 'c', 'd'].join(File.separator)
ContractDownloader contractDownloader = new ContractDownloader(stubDownloader,
stubConfiguration, contractPath, '', '', '')
ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties()
and:
stubDownloader.downloadAndUnpackStubJar(_) >> new AbstractMap.SimpleEntry(stubConfiguration, file)
when:
contractDownloader.unpackedDownloadedContracts(properties)
ContractDownloader.InclusionProperties inclusionProperties = contractDownloader.createNewInclusionProperties(file)
then:
properties.includedContracts.startsWith('^')
properties.includedContracts.endsWith('$')
properties.includedContracts.contains(fileSeparated('/some/path/to/somewhere(/)?.*/a/b/c/d/.*'))
properties.includedRootFolderAntPattern == "**/a/b/c/d/**/"
inclusionProperties.includedContracts.startsWith('^')
inclusionProperties.includedContracts.endsWith('$')
inclusionProperties.includedContracts.contains(fileSeparated('/some/path/to/somewhere(/)?.*/a/b/c/d/.*'))
inclusionProperties.includedRootFolderAntPattern == "**/a/b/c/d/**/"
}
def 'should set inclusion pattern on config when path pattern was explicitly provided without a separator at the beginning'() {
@@ -52,16 +47,14 @@ class ContractDownloaderSpec extends Specification {
String contractPath = ['a', 'b', 'c', 'd'].join(File.separator)
ContractDownloader contractDownloader = new ContractDownloader(stubDownloader,
stubConfiguration, contractPath, '', '', '')
ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties()
and:
stubDownloader.downloadAndUnpackStubJar(_) >> new AbstractMap.SimpleEntry(stubConfiguration, file)
when:
contractDownloader.unpackedDownloadedContracts(properties)
ContractDownloader.InclusionProperties inclusionProperties =
contractDownloader.createNewInclusionProperties(file)
then:
properties.includedContracts.startsWith('^')
properties.includedContracts.endsWith('$')
properties.includedContracts.contains(fileSeparated('/some/path/to/somewhere(/)?.*/a/b/c/d/.*'))
properties.includedRootFolderAntPattern == "**/a/b/c/d/**/"
inclusionProperties.includedContracts.startsWith('^')
inclusionProperties.includedContracts.endsWith('$')
inclusionProperties.includedContracts.contains(fileSeparated('/some/path/to/somewhere(/)?.*/a/b/c/d/.*'))
inclusionProperties.includedRootFolderAntPattern == "**/a/b/c/d/**/"
}
private static String fileSeparated(String string) {

View File

@@ -43,28 +43,41 @@ import org.springframework.cloud.contract.verifier.wiremock.DslToWireMockClientC
class RecursiveFilesConverter {
private final StubGeneratorProvider holder
private final ContractVerifierConfigProperties props
private final File outMappingsDir
private final File contractsDslDir
private final List<String> excludedFiles
private final String includedContracts
private final boolean excludeBuildFolders
// Use constructor without ContractVerifierConfigProperties
@Deprecated
RecursiveFilesConverter(ContractVerifierConfigProperties props, StubGeneratorProvider holder = null) {
this.props = props
this.outMappingsDir = props.stubsOutputDir
this.holder = holder ?: new StubGeneratorProvider()
this(props.stubsOutputDir, props.contractsDslDir, props.excludedFiles, props.includedContracts, props.excludeBuildFolders, holder)
}
RecursiveFilesConverter(ContractVerifierConfigProperties props, File outMappingsDir, StubGeneratorProvider holder = null) {
this.props = props
this.outMappingsDir = outMappingsDir
// Use constructor without ContractVerifierConfigProperties
@Deprecated
RecursiveFilesConverter(ContractVerifierConfigProperties props, File stubsOutputDir, StubGeneratorProvider holder = null) {
this(stubsOutputDir, props.contractsDslDir, props.excludedFiles, props.includedContracts, props.excludeBuildFolders, holder)
}
RecursiveFilesConverter(File stubsOutputDir, File contractsDslDir, List<String> excludedFiles,
String includedContracts, boolean excludeBuildFolders, StubGeneratorProvider holder = null) {
this.outMappingsDir = stubsOutputDir
this.contractsDslDir = contractsDslDir
this.excludedFiles = excludedFiles
this.includedContracts = includedContracts
this.excludeBuildFolders = excludeBuildFolders
this.holder = holder ?: new StubGeneratorProvider()
}
void processFiles() {
ContractFileScanner scanner = new ContractFileScannerBuilder()
.baseDir(props.contractsDslDir)
.excluded(props.excludedFiles as Set)
.baseDir(contractsDslDir)
.excluded(excludedFiles as Set)
.ignored([] as Set)
.included([] as Set)
.includeMatcher(props.includedContracts)
.includeMatcher(includedContracts)
.build()
ListMultimap<Path, ContractMetadata> contracts = scanner.findContracts()
if (log.isDebugEnabled()) {
@@ -81,7 +94,7 @@ class RecursiveFilesConverter {
holder.converterForName(sourceFile.name)
try {
String path = sourceFile.path
if (props.isExcludeBuildFolders()
if (excludeBuildFolders
&& (
matchesPath(path, "target") || matchesPath(path, "build"))) {
if (log.isDebugEnabled()) {
@@ -131,7 +144,7 @@ class RecursiveFilesConverter {
}
private Path createAndReturnTargetDirectory(File sourceFile) {
Path relativePath = Paths.get(props.contractsDslDir.toURI()).
Path relativePath = Paths.get(contractsDslDir.toURI()).
relativize(sourceFile.parentFile.toPath())
Path absoluteTargetPath = outMappingsDir.toPath().resolve(relativePath)
Files.createDirectories(absoluteTargetPath)

View File

@@ -25,7 +25,6 @@ import org.junit.rules.TemporaryFolder
import spock.lang.Specification
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import org.springframework.cloud.contract.verifier.file.ContractMetadata
import org.springframework.util.FileSystemUtils
@@ -50,23 +49,22 @@ class RecursiveFilesConverterSpec extends Specification {
def "should recursively convert all matching files"() {
given:
ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties()
File originalSourceRootDirectory = new File(this.getClass()
.getResource("/converter/source").toURI())
properties.contractsDslDir = tmpFolder.newFolder("source")
properties.stubsOutputDir = tmpFolder.newFolder("target")
File contractsDslDir = tmpFolder.newFolder("source")
File stubsOutputDir = tmpFolder.newFolder("target")
FileSystemUtils
.copyRecursively(originalSourceRootDirectory, properties.contractsDslDir)
.copyRecursively(originalSourceRootDirectory, contractsDslDir)
and:
RecursiveFilesConverter recursiveFilesConverter = new RecursiveFilesConverter(properties)
RecursiveFilesConverter recursiveFilesConverter = new RecursiveFilesConverter(stubsOutputDir, contractsDslDir, new ArrayList<>(), ".*", false)
when:
recursiveFilesConverter.processFiles()
then:
Collection<File> createdFiles = [] as List
properties.stubsOutputDir.
stubsOutputDir.
eachFileRecurse(FileType.FILES) { createdFiles << it }
Set<String> relativizedCreatedFiles =
getRelativePathsForFilesInDirectory(createdFiles, properties.stubsOutputDir)
getRelativePathsForFilesInDirectory(createdFiles, stubsOutputDir)
relativizedCreatedFiles == EXPECTED_TARGET_FILES
and:
createdFiles.each { assert it.text.contains("uuid") }
@@ -74,24 +72,23 @@ class RecursiveFilesConverterSpec extends Specification {
def "should recursively convert matching files with exlusions"() {
given:
ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties()
File originalSourceRootDirectory = new File(this.getClass()
.getResource("/converter/source").toURI())
properties.contractsDslDir = tmpFolder.newFolder("source")
properties.stubsOutputDir = tmpFolder.newFolder("target")
properties.excludedFiles = ["dir1/**"]
File contractsDslDir = tmpFolder.newFolder("source")
File stubsOutputDir = tmpFolder.newFolder("target")
List<String> excludedFiles = ["dir1/**"]
FileSystemUtils
.copyRecursively(originalSourceRootDirectory, properties.contractsDslDir)
.copyRecursively(originalSourceRootDirectory, contractsDslDir)
and:
RecursiveFilesConverter recursiveFilesConverter = new RecursiveFilesConverter(properties)
RecursiveFilesConverter recursiveFilesConverter = new RecursiveFilesConverter(stubsOutputDir, contractsDslDir, excludedFiles, ".*", false)
when:
recursiveFilesConverter.processFiles()
then:
Collection<File> createdFiles = [] as List
properties.stubsOutputDir.
stubsOutputDir.
eachFileRecurse(FileType.FILES) { createdFiles << it }
Set<String> relativizedCreatedFiles =
getRelativePathsForFilesInDirectory(createdFiles, properties.stubsOutputDir)
getRelativePathsForFilesInDirectory(createdFiles, stubsOutputDir)
[Paths.get("dslRoot.json"), Paths.
get("dir2/dsl2.json")] as Set == relativizedCreatedFiles as Set
and:
@@ -100,7 +97,7 @@ class RecursiveFilesConverterSpec extends Specification {
def "on failure should break processing and throw meaningful exception"() {
given:
def sourceFile = tmpFolder.newFile("test.groovy")
File sourceFile = tmpFolder.newFile("test.groovy")
sourceFile.text = """\
org.springframework.cloud.contract.spec.Contract.make {
request {
@@ -112,28 +109,27 @@ org.springframework.cloud.contract.spec.Contract.make {
}
}"""
and:
def stubGenerator = Stub(StubGenerator)
StubGenerator stubGenerator = Stub(StubGenerator)
stubGenerator.canHandleFileName(_) >> { true }
stubGenerator.convertContents(_, _) >> {
throw new NullPointerException("Test conversion error")
}
stubGenerator
.generateOutputFileNameForInput(_) >> { String inputFileName -> "${inputFileName}2" }
ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties()
properties.contractsDslDir = tmpFolder.root
properties.stubsOutputDir = tmpFolder.root
RecursiveFilesConverter recursiveFilesConverter = new RecursiveFilesConverter(properties, new StubGeneratorProvider([stubGenerator]))
File contractsDslDir = tmpFolder.root
File stubsOutputDir = tmpFolder.root
RecursiveFilesConverter recursiveFilesConverter = new RecursiveFilesConverter(stubsOutputDir, contractsDslDir, new ArrayList<>(), ".*", false, new StubGeneratorProvider([stubGenerator]))
when:
recursiveFilesConverter.processFiles()
then:
def e = thrown(ConversionContractVerifierException)
ConversionContractVerifierException e = thrown(ConversionContractVerifierException)
e.message?.contains(sourceFile.name)
e.cause?.message == "Test conversion error"
}
def "should convert contract into stub using all possible converters"() {
given:
def sourceFile = tmpFolder.newFile("test.groovy")
File sourceFile = tmpFolder.newFile("test.groovy")
sourceFile.text = """
org.springframework.cloud.contract.spec.Contract.make {
request {
@@ -150,11 +146,10 @@ org.springframework.cloud.contract.spec.Contract.make {
and:
StubGenerator stubGenerator2 = stubGenerator("bar")
and:
ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties()
properties.contractsDslDir = tmpFolder.root
properties.stubsOutputDir = tmpFolder.root
File contractsDslDir = tmpFolder.root
File stubsOutputDir = tmpFolder.root
and:
RecursiveFilesConverter recursiveFilesConverter = new RecursiveFilesConverter(properties, new StubGeneratorProvider([stubGenerator1, stubGenerator2]))
RecursiveFilesConverter recursiveFilesConverter = new RecursiveFilesConverter(stubsOutputDir, contractsDslDir, new ArrayList<>(), ".*", false, new StubGeneratorProvider([stubGenerator1, stubGenerator2]))
when:
recursiveFilesConverter.processFiles()
then:
@@ -163,7 +158,7 @@ org.springframework.cloud.contract.spec.Contract.make {
def "should not create stub file when generated stub is empty"() {
given:
def sourceFile = tmpFolder.newFile("test.groovy")
File sourceFile = tmpFolder.newFile("test.groovy")
sourceFile.text = """
org.springframework.cloud.contract.spec.Contract.make {
request {
@@ -178,15 +173,14 @@ org.springframework.cloud.contract.spec.Contract.make {
and:
StubGenerator stubGenerator = stubGenerator("")
and:
ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties()
properties.contractsDslDir = tmpFolder.root
properties.stubsOutputDir = tmpFolder.newFolder("target")
File contractsDslDir = tmpFolder.root
File stubsOutputDir = tmpFolder.newFolder("target")
and:
RecursiveFilesConverter recursiveFilesConverter = new RecursiveFilesConverter(properties, new StubGeneratorProvider([stubGenerator]))
RecursiveFilesConverter recursiveFilesConverter = new RecursiveFilesConverter(stubsOutputDir, contractsDslDir, new ArrayList<>(), ".*", false, new StubGeneratorProvider([stubGenerator]))
when:
recursiveFilesConverter.processFiles()
then:
properties.stubsOutputDir.list().toList().isEmpty()
stubsOutputDir.list().toList().isEmpty()
}
private static Set<Path> getRelativePathsForFilesInDirectory(Collection<File> createdFiles, File targetRootDirectory) {

View File

@@ -17,133 +17,133 @@
package org.springframework.cloud.contract.verifier.plugin
import groovy.transform.CompileStatic
import groovy.transform.ToString
import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties
import org.springframework.cloud.contract.verifier.config.TestFramework
import org.springframework.cloud.contract.verifier.config.TestMode
import javax.inject.Inject
/**
* @author Marcin Grzejszczak
* @author Anatoliy Balakirev
*/
@ToString
@CompileStatic
class ContractVerifierExtension {
private static final Log log = LogFactory.getLog(ContractVerifierExtension)
/**
* For which unit test library tests should be generated
* @deprecated - use {@code testFramework}
*/
@Deprecated
TestFramework targetFramework
@Deprecated
void setTargetFramework(TestFramework targetFramework) {
log.warn("Please use the [testFramework] field. This one is deprecated")
setTestFramework(targetFramework)
this.testFramework.set(targetFramework)
}
@Deprecated
TestFramework getTargetFramework() {
return getTestFramework()
return getTestFramework().get()
}
/**
* For which unit test library tests should be generated
*/
TestFramework testFramework = TestFramework.JUNIT
Property<TestFramework> testFramework
/**
* Which mechanism should be used to invoke REST calls during tests
*/
TestMode testMode = TestMode.MOCKMVC
Property<TestMode> testMode
/**
* Base package for generated tests
*/
String basePackageForTests
Property<String> basePackageForTests
/**
* Class which all generated tests should extend
*/
String baseClassForTests
Property<String> baseClassForTests
/**
* Suffix for generated test classes, like Spec or Test
*/
String nameSuffixForTests
Property<String> nameSuffixForTests
/**
* Rule class that should be added to generated tests
*/
String ruleClassForTests
Property<String> ruleClassForTests
/**
* Patterns that should not be taken into account for processing
*/
List<String> excludedFiles = []
ListProperty<String> excludedFiles
/**
* Patterns that should be taken into account for processing
*/
List<String> includedFiles = []
ListProperty<String> includedFiles
/**
* Patterns for which generated tests should be @Ignored
*/
List<String> ignoredFiles = []
ListProperty<String> ignoredFiles
/**
* Imports that should be added to generated tests
*/
String[] imports = []
ListProperty<String> imports
/**
* Static imports that should be added to generated tests
*/
String[] staticImports = []
ListProperty<String> staticImports
/**
* Directory containing contracts written using the GroovyDSL
*/
File contractsDslDir
DirectoryProperty contractsDslDir
/**
* Test source directory where tests generated from Groovy DSL should be placed
*/
File generatedTestSourcesDir
DirectoryProperty generatedTestSourcesDir
/**
* Test resource directory where tests generated from Groovy DSL should be referenced
*/
File generatedTestResourcesDir
DirectoryProperty generatedTestResourcesDir
/**
* Dir where the generated stubs from Groovy DSL should be placed.
* You can then mention them in your packaging task to create jar with stubs
*/
File stubsOutputDir
DirectoryProperty stubsOutputDir
/**
* Suffix for the generated Stubs Jar task
*/
String stubsSuffix = 'stubs'
Property<String> stubsSuffix
/**
* Incubating feature. You can check the size of JSON arrays. If not turned on
* explicitly will be disabled.
*/
Boolean assertJsonSize = false
Property<Boolean> assertJsonSize
/**
* When enabled, this flag will tell stub runner to throw an exception when no stubs /
* contracts were found.
*/
boolean failOnNoContracts = true
Property<Boolean> failOnNoContracts
/**
* If set to true then if any contracts that are in progress are found, will break the
@@ -151,14 +151,14 @@ class ContractVerifierExtension {
* contracts in progress and take into consideration that you might be causing false
* positive test execution results on the consumer side.
*/
boolean failOnInProgress = true;
Property<Boolean> failOnInProgress
ContractRepository contractRepository = new ContractRepository()
ContractRepository contractRepository
/**
* Dependency that contains packaged contracts
*/
Dependency contractDependency = new Dependency()
Dependency contractDependency
/**
* The path in the JAR with all the contracts where contracts for this particular service lay.
@@ -167,12 +167,12 @@ class ContractVerifierExtension {
* If {@code groupid} is {@code com.example} and {@code artifactid} is {@code service} then the resolved path will be
* {@code /com/example/artifactid}
*/
String contractsPath
Property<String> contractsPath
/**
* Picks the mode in which stubs will be found and registered
*/
StubRunnerProperties.StubsMode contractsMode = StubRunnerProperties.StubsMode.CLASSPATH
Property<StubRunnerProperties.StubsMode> contractsMode
/**
* A package that contains all the base clases for generated tests. If your contract resides in a location
@@ -181,7 +181,7 @@ class ContractVerifierExtension {
* have the package {@code com.example.contracts.base} and name {@code ExampleV1Base}. As you can see
* it will take the two last folders to and attach {@code Base} to its name.
*/
String packageWithBaseClasses
Property<String> packageWithBaseClasses
/**
* A way to override any base class mappings. The keys are regular expressions on the package name
@@ -194,14 +194,14 @@ class ContractVerifierExtension {
* When a contract's package matches the provided regular expression then extending class will be the one
* provided in the map - in this case {@code com.example.SomeBaseClass}
*/
Map<String, String> baseClassMappings = [:]
MapProperty<String, String> baseClassMappings
/**
* If set to true then the {@code target} or {@code build} folders are getting
* excluded from any operations. This is used out of the box when working with
* common repo with contracts.
*/
boolean excludeBuildFolders = false
Property<Boolean> excludeBuildFolders
/**
* If set to {@code true} will not assert whether the downloaded stubs / contract
@@ -210,23 +210,23 @@ class ContractVerifierExtension {
* @deprecated - with 2.1.0 this option is redundant
*/
@Deprecated
boolean contractsSnapshotCheckSkip = false
Property<Boolean> contractsSnapshotCheckSkip
/**
* If set to {@code false} will NOT delete stubs from a temporary
* folder after running tests
*/
boolean deleteStubsAfterTest = true
Property<Boolean> deleteStubsAfterTest
/**
* If {@code true} then will convert contracts to a YAML representation
*/
boolean convertToYaml = false
Property<Boolean> convertToYaml
/**
* Map of properties that can be passed to custom {@link org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder}
*/
Map<String, String> contractsProperties = [:]
MapProperty<String, String> contractsProperties
void contractDependency(@DelegatesTo(Dependency) Closure closure) {
closure.delegate = contractDependency
@@ -243,175 +243,167 @@ class ContractVerifierExtension {
closure.call()
}
void contractsProperties(Map<String, String> props) {
contractsProperties = props
}
/**
* Is set to true will not provide the default publication task
*/
boolean disableStubPublication = false
Property<Boolean> disableStubPublication
void disableStubPublication(boolean disableStubPublication) {
this.disableStubPublication = disableStubPublication
// Added for backward compatibility only. Use setter of dedicated enum type
@Deprecated
void setTestMode(String testMode) {
if (testMode != null) {
this.testMode.set(TestMode.valueOf(testMode.toUpperCase()))
}
}
void failOnNoContracts(boolean failOnNoContracts) {
this.failOnNoContracts = failOnNoContracts
// Added for backward compatibility only. Use setter of dedicated enum type
@Deprecated
void setTestFramework(String testFramework) {
if (testFramework != null) {
this.testFramework.set(TestFramework.valueOf(testFramework.toUpperCase()))
}
}
void failOnInProgress(boolean failOnInProgress) {
this.failOnInProgress = failOnInProgress
// Added for backward compatibility only. Use setter of dedicated enum type
@Deprecated
void setContractsMode(String contractsMode) {
if (contractsMode != null) {
this.contractsMode.set(StubRunnerProperties.StubsMode.valueOf(contractsMode.toUpperCase()))
}
}
ContractVerifierExtension copy() {
return new ContractVerifierExtension(
testFramework: this.testFramework,
testMode: this.testMode,
basePackageForTests: this.basePackageForTests,
baseClassForTests: this.baseClassForTests,
nameSuffixForTests: this.nameSuffixForTests,
ruleClassForTests: this.ruleClassForTests,
excludedFiles: new ArrayList<String>(this.excludedFiles),
includedFiles: new ArrayList<String>(this.includedFiles),
ignoredFiles: new ArrayList<String>(this.ignoredFiles),
imports: Arrays.asList(this.imports).toArray() as String[],
staticImports: Arrays.asList(this.staticImports).toArray() as String[],
contractsDslDir: this.contractsDslDir,
generatedTestSourcesDir: this.generatedTestSourcesDir,
generatedTestResourcesDir: this.generatedTestResourcesDir,
stubsOutputDir: this.stubsOutputDir,
stubsSuffix: this.stubsSuffix,
assertJsonSize: this.assertJsonSize,
failOnNoContracts: this.failOnNoContracts,
failOnInProgress: this.failOnInProgress,
contractRepository: new ContractRepository(
repositoryUrl: this.contractRepository.repositoryUrl,
username: this.contractRepository.username,
password: this.contractRepository.password,
proxyPort: this.contractRepository.proxyPort,
proxyHost: this.contractRepository.proxyHost,
cacheDownloadedContracts: this.contractRepository.cacheDownloadedContracts,
),
contractDependency: new Dependency(
groupId: this.contractDependency.groupId,
artifactId: this.contractDependency.artifactId,
classifier: this.contractDependency.classifier,
version: this.contractDependency.version,
stringNotation: this.contractDependency.stringNotation
),
contractsPath: this.contractsPath,
contractsMode: this.contractsMode,
packageWithBaseClasses: this.packageWithBaseClasses,
baseClassMappings: new HashMap<String, String>(this.baseClassMappings),
excludeBuildFolders: this.excludeBuildFolders,
deleteStubsAfterTest: this.deleteStubsAfterTest,
convertToYaml: this.convertToYaml,
contractsProperties: new HashMap<String, String>(this.contractsProperties)
)
@Inject
ContractVerifierExtension(ObjectFactory objects) {
this.testFramework = objects.property(TestFramework).convention(TestFramework.JUNIT)
this.testMode = objects.property(TestMode).convention(TestMode.MOCKMVC)
this.basePackageForTests = objects.property(String)
this.baseClassForTests = objects.property(String)
this.nameSuffixForTests = objects.property(String)
this.ruleClassForTests = objects.property(String)
this.excludedFiles = objects.listProperty(String).convention([])
this.includedFiles = objects.listProperty(String).convention([])
this.ignoredFiles = objects.listProperty(String).convention([])
this.imports = objects.listProperty(String).convention([])
this.staticImports = objects.listProperty(String).convention([])
this.contractsDslDir = objects.directoryProperty()
this.generatedTestSourcesDir = objects.directoryProperty()
this.generatedTestResourcesDir = objects.directoryProperty()
this.stubsOutputDir = objects.directoryProperty()
this.stubsSuffix = objects.property(String).convention("stubs")
this.assertJsonSize = objects.property(Boolean).convention(false)
this.failOnNoContracts = objects.property(Boolean).convention(true)
this.failOnInProgress = objects.property(Boolean).convention(true)
this.contractRepository = new ContractRepository(objects)
this.contractDependency = new Dependency(objects)
this.contractsPath = objects.property(String)
this.contractsMode = objects.property(StubRunnerProperties.StubsMode).convention(StubRunnerProperties.StubsMode.CLASSPATH)
this.packageWithBaseClasses = objects.property(String)
this.baseClassMappings = objects.mapProperty(String, String).convention([:])
this.excludeBuildFolders = objects.property(Boolean).convention(false)
this.contractsSnapshotCheckSkip = objects.property(Boolean).convention(false)
this.deleteStubsAfterTest = objects.property(Boolean).convention(true)
this.convertToYaml = objects.property(Boolean).convention(false)
this.contractsProperties = objects.mapProperty(String, String).convention([:])
this.disableStubPublication = objects.property(Boolean).convention(false)
}
@ToString(includeNames = true, includePackage = false)
static class Dependency {
String groupId
String artifactId
String classifier
String version
String stringNotation
@Input
@Optional
Property<String> groupId
@Input
@Optional
Property<String> artifactId
@Input
@Optional
Property<String> version
@Input
@Optional
Property<String> classifier
@Input
@Optional
Property<String> stringNotation
void groupId(String groupId) {
this.groupId = groupId
@Inject
Dependency(ObjectFactory objects) {
groupId = objects.property(String)
artifactId = objects.property(String)
version = objects.property(String)
classifier = objects.property(String)
stringNotation = objects.property(String)
}
void artifactId(String artifactId) {
this.artifactId = artifactId
}
void classifier(String classifier) {
this.classifier = classifier
}
void version(String version) {
this.version = version
}
void stringNotation(String stringNotation) {
this.stringNotation = stringNotation
@Override
String toString() {
return "Dependency{" +
"groupId=" + groupId.getOrNull() +
", artifactId=" + artifactId.getOrNull() +
", classifier=" + classifier.getOrNull() +
", version=" + version.getOrNull() +
", stringNotation=" + stringNotation.getOrNull() +
'}'
}
}
@ToString(includeNames = true, includePackage = false)
static class BaseClassMapping {
private final Map<String, String> delegate
private final MapProperty<String, String> delegate
BaseClassMapping(Map<String, String> delegate) {
private BaseClassMapping(MapProperty<String, String> delegate) {
this.delegate = delegate
}
void baseClassMapping(String packageRegex, String fqnBaseClass) {
this.delegate[packageRegex] = fqnBaseClass
delegate.put(packageRegex, fqnBaseClass)
}
void baseClassMapping(Map mapping) {
this.delegate.putAll(mapping)
void baseClassMapping(Map<String, String> mapping) {
delegate.putAll(mapping)
}
}
@ToString(includeNames = true, includePackage = false)
// This class is used as an input to the tasks, so all fields are marked as `@Input` to allow incremental build
static class ContractRepository {
@Input
@Optional
Property<String> repositoryUrl
@Input
@Optional
Property<String> username
@Input
@Optional
Property<String> password
@Input
@Optional
Property<Integer> proxyPort
@Input
@Optional
Property<String> proxyHost
/**
* Repository URL
* If set to true then will cache the folder where non snapshot contract artifacts got downloaded.
*/
String repositoryUrl
@Input
Property<Boolean> cacheDownloadedContracts
/**
* Repository username
*/
String username
/**
* Repository password
*/
String password
/**
* Repository proxy port
*/
Integer proxyPort
/**
* Repository proxy host
*/
String proxyHost
/**
* If set to true then will cache the folder where non snapshot contract artifacts
* got downloaded.
*/
boolean cacheDownloadedContracts = true
void repositoryUrl(String repositoryUrl) {
this.repositoryUrl = repositoryUrl
@Inject
ContractRepository(ObjectFactory objects) {
this.repositoryUrl = objects.property(String)
this.username = objects.property(String)
this.password = objects.property(String)
this.proxyHost = objects.property(String)
this.proxyPort = objects.property(Integer)
this.cacheDownloadedContracts = objects.property(Boolean).convention(true)
}
void username(String username) {
this.username = username
}
void password(String password) {
this.password = password
}
void proxyPort(Integer proxyPort) {
this.proxyPort = proxyPort
}
void proxyHost(String proxyHost) {
this.proxyHost = proxyHost
}
void cacheDownloadedContracts(boolean cacheDownloadedContracts) {
this.cacheDownloadedContracts = cacheDownloadedContracts
@Override
String toString() {
return "ContractRepository{" +
"repositoryUrl=" + repositoryUrl.getOrNull() +
", username=" + username.getOrNull() +
", password=" + password.getOrNull() +
", proxyPort=" + proxyPort.getOrNull() +
", proxyHost=" + proxyHost.getOrNull() +
", cacheDownloadedContracts=" + cacheDownloadedContracts.get() +
'}'
}
}
}
}

View File

@@ -16,115 +16,138 @@
package org.springframework.cloud.contract.verifier.plugin
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.internal.ConventionTask
import org.gradle.api.logging.Logger
import org.gradle.api.Project
import org.gradle.api.file.CopySpec
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.WorkResult
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import org.springframework.cloud.contract.verifier.converter.ToYamlConverter
// TODO: Convert to incremental task: https://docs.gradle.org/current/userguide/custom_tasks.html#incremental_tasks
/**
* Task that copies the contracts in order for the jar task to
* generate the jar. It takes into consideration the inclusion
* patterns when working with repo with shared contracts.
*
* @author Marcin Grzejszczak
* @author Anatoliy Balakirev
* @since 1.0.2
*/
@PackageScope
@CompileStatic
class ContractsCopyTask extends ConventionTask {
private static final String ORIGINAL_PATH = "original"
class ContractsCopyTask extends DefaultTask {
ContractVerifierExtension extension
GradleContractsDownloader downloader
static final String TASK_NAME = 'copyContracts'
static final String CONTRACTS = "contracts"
static final String BACKUP = "original"
@Nested
Config config
static class Config {
@Input
Provider<Boolean> convertToYaml
@Input
Provider<Boolean> excludeBuildFolders
@Input
Provider<Boolean> failOnNoContracts
@Input
Provider<String> includedRootFolderAntPattern
@InputDirectory
Provider<Directory> contractsDirectory
@OutputDirectory
DirectoryProperty copiedContractsFolder
@Optional
@OutputDirectory
DirectoryProperty backupContractsFolder
}
@TaskAction
void copy() {
ContractVerifierConfigProperties props = ExtensionToProperties.fromExtension(getExtension())
File file = getDownloader().downloadAndUnpackContractsIfRequired(getExtension(), props)
file = contractsSubDirIfPresent(logger, file)
throwExceptionWhenFailOnNoContracts(file)
String antPattern = "${props.includedRootFolderAntPattern}*.*"
void sync() {
File contractsDirectory = config.contractsDirectory.get().asFile
throwExceptionWhenFailOnNoContracts(contractsDirectory)
String antPattern = "${config.includedRootFolderAntPattern.get()}*.*"
String slashSeparatedGroupId = project.group.toString().replace(".", File.separator)
String slashSeparatedAntPattern = antPattern.replace(slashSeparatedGroupId, project.group.toString())
String root = root(props)
File outputContractsFolder = outputContractsFolder(root)
project.logger.info("Downloading and unpacking files from [$file] to [$outputContractsFolder]. The inclusion ant patterns are [${antPattern}] and [${slashSeparatedAntPattern}]")
copy(file, antPattern, slashSeparatedAntPattern, props, outputContractsFolder)
if (getExtension().isConvertToYaml()) {
convertBackedUpDslsToYaml(root, file, antPattern, slashSeparatedAntPattern, props, outputContractsFolder)
File output = config.copiedContractsFolder.get().asFile
logger.info("Downloading and unpacking files from [${contractsDirectory}()] to [$output]. The inclusion ant patterns are [${antPattern}] and [${slashSeparatedAntPattern}]")
sync(contractsDirectory, antPattern, slashSeparatedAntPattern, config.excludeBuildFolders.get(), output)
if (config.convertToYaml.get()) {
convertBackedUpDslsToYaml(contractsDirectory, antPattern, slashSeparatedAntPattern, output, config.excludeBuildFolders.get())
}
}
static Config fromExtension(ContractVerifierExtension extension, TaskProvider<InitContractsTask> initContractsTask, String root, Project project) {
return new Config(
convertToYaml: extension.convertToYaml,
excludeBuildFolders: extension.excludeBuildFolders,
failOnNoContracts: extension.failOnNoContracts,
includedRootFolderAntPattern: initContractsTask.flatMap { it.config.includedRootFolderAntPattern },
contractsDirectory: initContractsTask.flatMap { it.config.initialisedContractsDirectory },
copiedContractsFolder: createTaskOutput(root, extension.stubsOutputDir, ContractsCopyTask.CONTRACTS, project),
backupContractsFolder: createTaskOutput(root, extension.stubsOutputDir, ContractsCopyTask.BACKUP, project)
)
}
private void convertBackedUpDslsToYaml(File file, String antPattern, String slashSeparatedAntPattern, File outputContractsFolder, boolean excludeBuildFolders) {
sync(file, antPattern, slashSeparatedAntPattern, excludeBuildFolders, config.backupContractsFolder.get().asFile)
ToYamlConverter.replaceContractWithYaml(outputContractsFolder)
logger.info("Replaced DSL files with their YAML representation at [" + outputContractsFolder + "]")
}
protected WorkResult sync(File file, String antPattern, String slashSeparatedAntPattern, boolean excludeBuildFolders, File outputContractsFolder) {
// TODO: Is there any better way to make it statically compiled, avoiding explicit creation of new Action?
// sync will remove files from target if they are removed from source. So using it here instead of copy:
return project.sync(new Action<CopySpec>() {
@Override
void execute(final CopySpec spec) {
spec.with {
from(file)
// by default group id is slash separated...
include(antPattern)
// ...we also want to allow dot separation
include(slashSeparatedAntPattern)
if (excludeBuildFolders) {
exclude "**/target/**", "**/build/**", "**/.mvn/**", "**/.gradle/**"
}
into(outputContractsFolder)
}
}
})
}
private static DirectoryProperty createTaskOutput(String root, DirectoryProperty stubsOutputDir, String suffix, Project project) {
Provider<Directory> provider = stubsOutputDir.flatMap {
Directory dir = it
File output = project.file("${dir.asFile}/${root}/${suffix}")
DirectoryProperty property = project.objects.directoryProperty()
property.set(output)
return property
}
DirectoryProperty property = project.objects.directoryProperty();
property.set(provider)
return property
}
private void throwExceptionWhenFailOnNoContracts(File file) {
if (getExtension().isFailOnNoContracts() && (!file.exists() || file.listFiles().length == 0)) {
if (config.failOnNoContracts.get() && (!file.exists() || file.listFiles().length == 0)) {
throw new GradleException("Contracts could not be found: ["
+ file.getAbsolutePath()
+ "] .\nPlease make sure that the contracts were defined, or set the [failOnNoContracts] flag to [false]")
}
}
@CompileDynamic
private File outputContractsFolder(String root) {
File outputContractsFolder = outputFolder(root, "contracts")
ext.contractsDslDir = outputContractsFolder
return outputContractsFolder
}
@CompileDynamic
private String root(ContractVerifierConfigProperties props) {
String root = OutputFolderBuilder.buildRootPath(project)
ext.contractVerifierConfigProperties = props
return root
}
@CompileDynamic
private void convertBackedUpDslsToYaml(String root, File file, String antPattern, String slashSeparatedAntPattern, ContractVerifierConfigProperties props, File outputContractsFolder) {
File originalContracts = outputFolder(root, ORIGINAL_PATH)
copy(file, antPattern, slashSeparatedAntPattern, props, originalContracts)
ToYamlConverter.replaceContractWithYaml(outputContractsFolder)
project.logger.
info("Replaced DSL files with their YAML representation at [" + ext.contractsDslDir + "]")
}
@CompileDynamic
protected WorkResult copy(File file, String antPattern, String slashSeparatedAntPattern, props, File outputContractsFolder) {
return project.copy {
from(file)
// by default group id is slash separated...
include(antPattern)
// ...we also want to allow dot separation
include(slashSeparatedAntPattern)
if (props.isExcludeBuildFolders()) {
exclude "**/target/**", "**/build/**", "**/.mvn/**", "**/.gradle/**"
}
into(outputContractsFolder)
}
}
@CompileStatic
private File outputFolder(String root, String suffix) {
return getExtension().stubsOutputDir != null ?
project.file("${getExtension().stubsOutputDir}/${root}/${suffix}") :
project.file("${project.buildDir}/stubs/${root}/${suffix}")
}
@CompileStatic
private File contractsSubDirIfPresent(Logger logger, File contractsDirectory) {
File contracts = new File(contractsDirectory, "contracts")
if (contracts.exists()) {
if (logger.isDebugEnabled()) {
logger.debug("Contracts folder found [" + contracts + "]")
}
contractsDirectory = contracts
}
return contractsDirectory
}
}
}

View File

@@ -1,30 +0,0 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.verifier.plugin
/**
* Allows to set the {@link Closure} that will modify the existing
* {@link ContractVerifierExtension}
*
* @author Marcin Grzejszczak
* @since 2.1.0
*/
interface ExtensionHolderSpec {
Closure getExtensionClosure()
void setExtensionClosure(Closure closure)
}

View File

@@ -1,40 +0,0 @@
package org.springframework.cloud.contract.verifier.plugin
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
/**
* @author Marcin Grzejszczak
*/
@PackageScope
@CompileStatic
class ExtensionToProperties {
protected static ContractVerifierConfigProperties fromExtension(ContractVerifierExtension extension) {
return new ContractVerifierConfigProperties(
testFramework: extension.getTestFramework(),
testMode: extension.getTestMode(),
basePackageForTests: extension.getBasePackageForTests(),
baseClassForTests: extension.getBaseClassForTests(),
nameSuffixForTests: extension.getNameSuffixForTests(),
ruleClassForTests: extension.getRuleClassForTests(),
excludedFiles: extension.getExcludedFiles(),
includedFiles: extension.getIncludedFiles(),
ignoredFiles: extension.getIgnoredFiles(),
imports: extension.getImports(),
staticImports: extension.getStaticImports(),
contractsDslDir: extension.getContractsDslDir(),
generatedTestSourcesDir: extension.getGeneratedTestSourcesDir(),
generatedTestResourcesDir: extension.getGeneratedTestResourcesDir(),
stubsOutputDir: extension.getStubsOutputDir(),
stubsSuffix: extension.getStubsSuffix(),
assertJsonSize: extension.getAssertJsonSize(),
packageWithBaseClasses: extension.getPackageWithBaseClasses(),
baseClassMappings: extension.getBaseClassMappings(),
excludeBuildFolders: extension.getExcludeBuildFolders(),
failOnInProgress: extension.getFailOnInProgress()
)
}
}

View File

@@ -16,66 +16,83 @@
package org.springframework.cloud.contract.verifier.plugin
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import org.gradle.api.Task
import org.gradle.api.internal.ConventionTask
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import org.gradle.api.tasks.TaskProvider
import org.springframework.cloud.contract.verifier.converter.RecursiveFilesConverter
//TODO: Implement as an incremental task: https://gradle.org/docs/current/userguide/custom_tasks.html#incremental_tasks ?
/**
* Generates stubs from the contracts. The name is WireMock related but the implementation
* can differ
* Generates stubs from the contracts.
*
* @author Marcin Grzejszczak
* @author Anatoliy Balakirev
* @since 2.0.0
*/
@CompileStatic
class GenerateClientStubsFromDslTask extends ConventionTask {
class GenerateClientStubsFromDslTask extends DefaultTask {
File stubsOutputDir
static final String TASK_NAME = 'generateClientStubs'
private static final String DEFAULT_MAPPINGS_FOLDER = 'mappings'
@Nested
Config config
ContractVerifierExtension configProperties
GradleContractsDownloader downloader
static class Config {
@InputDirectory
Provider<Directory> contractsDslDir
@Input
ListProperty<String> excludedFiles
@Input
Provider<Boolean> excludeBuildFolders
@OutputDirectory
Provider<Directory> stubsOutputDir
}
@TaskAction
void generate() {
logger.info("Stubs output dir [${getStubsOutputDir()}")
Task copyContractsTask = project.getTasksByName(SpringCloudContractVerifierGradlePlugin.COPY_CONTRACTS_TASK_NAME, false).first()
ContractVerifierConfigProperties props = props(copyContractsTask)
File contractsDslDir = contractsDslDir(copyContractsTask, props)
File output = config.stubsOutputDir.get().asFile
logger.info("Stubs output dir [${output}")
logger.info("Spring Cloud Contract Verifier Plugin: Invoking DSL to client stubs conversion")
props.contractsDslDir = contractsDslDir
props.includedContracts = ".*"
File outMappingsDir = OutputFolderBuilder.outputMappingsDir(project, getStubsOutputDir())
logger.info("Contracts dir is [${contractsDslDir}] output stubs dir is [${outMappingsDir}]")
RecursiveFilesConverter converter = new RecursiveFilesConverter(props, outMappingsDir)
logger.info("Contracts dir is [${config.contractsDslDir.get().asFile}] output stubs dir is [${output}]")
List<String> excludedFiles = config.excludedFiles.get()
RecursiveFilesConverter converter = new RecursiveFilesConverter(output,
config.contractsDslDir.get().asFile, excludedFiles, ".*", config.excludeBuildFolders.get())
converter.processFiles()
}
@CompileDynamic
private ContractVerifierConfigProperties props(Task task) {
try {
return task.ext.contractVerifierConfigProperties
}
catch (Exception e) {
project.logger.error("Couldn't retrieve the configuration property set by the copy contracts task", e)
ContractVerifierConfigProperties props = ExtensionToProperties.fromExtension(getConfigProperties())
getDownloader().downloadAndUnpackContractsIfRequired(getConfigProperties(), props)
return props
}
static Config fromExtension(ContractVerifierExtension extension, TaskProvider<ContractsCopyTask> copyContracts,
String root, Project project) {
return new Config(
contractsDslDir: copyContracts.flatMap { it.config.copiedContractsFolder },
excludedFiles: extension.excludedFiles,
excludeBuildFolders: extension.excludeBuildFolders,
stubsOutputDir: createTaskOutput(root, extension.stubsOutputDir, project)
)
}
@CompileDynamic
private File contractsDslDir(Task task, ContractVerifierConfigProperties props) {
try {
return task.ext.contractsDslDir
}
catch (Exception e) {
project.logger.error("Couldn't retrieve the contract dsl property set by the copy contracts task", e)
return props.contractsDslDir
private static DirectoryProperty createTaskOutput(String root, DirectoryProperty stubsOutputDir, Project project) {
Provider<Directory> provider = stubsOutputDir.flatMap {
Directory dir = it
File output = new File(dir.asFile, "${root}/${DEFAULT_MAPPINGS_FOLDER}")
DirectoryProperty property = project.objects.directoryProperty();
property.set(output)
return property
}
DirectoryProperty property = project.objects.directoryProperty();
property.set(provider)
return property
}
}

View File

@@ -16,56 +16,121 @@
package org.springframework.cloud.contract.verifier.plugin
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.Task
import org.gradle.api.internal.ConventionTask
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.springframework.cloud.contract.spec.Contract
import org.gradle.api.tasks.TaskProvider
import org.springframework.cloud.contract.spec.ContractVerifierException
import org.springframework.cloud.contract.verifier.TestGenerator
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import org.springframework.cloud.contract.verifier.config.TestFramework
import static org.springframework.cloud.contract.verifier.plugin.SpringCloudContractVerifierGradlePlugin.COPY_CONTRACTS_TASK_NAME
import org.springframework.cloud.contract.verifier.config.TestMode
/**
* Task used to generate server side tests
*
* @author Marcin Grzejszczak
* @author Anatoliy Balakirev
* @since 1.0.0
*/
@CompileStatic
class GenerateServerTestsTask extends ConventionTask {
class GenerateServerTestsTask extends DefaultTask {
static final String TASK_NAME = 'generateContractTests'
@Nested
Config config
File generatedTestSourcesDir
static class Config {
@InputDirectory
Provider<Directory> contractsDslDir
@Input
Provider<String> includedContracts
@Input
@Optional
Provider<String> nameSuffixForTests
@Input
@Optional
Provider<String> basePackageForTests
@Input
@Optional
Provider<String> baseClassForTests
@Input
@Optional
Provider<String> packageWithBaseClasses
@Input
ListProperty<String> excludedFiles
@Input
ListProperty<String> ignoredFiles
@Input
ListProperty<String> includedFiles
@Input
ListProperty<String> imports
@Input
ListProperty<String> staticImports
@Input
Provider<TestMode> testMode
@Input
Provider<TestFramework> testFramework
@Input
MapProperty<String, String> baseClassMappings
@Input
Provider<Boolean> assertJsonSize
@Input
Provider<Boolean> failOnInProgress
//TODO: How to deal with @Input*, @Output* and that domain object?
ContractVerifierExtension configProperties
GradleContractsDownloader downloader
@OutputDirectory
DirectoryProperty generatedTestSourcesDir
@OutputDirectory
DirectoryProperty generatedTestResourcesDir
}
@TaskAction
void generate() {
logger.info("Generated test sources dir [${getGeneratedTestSourcesDir()}]")
Task copyContractsTask = project.getTasksByName(COPY_CONTRACTS_TASK_NAME, false).first()
ContractVerifierConfigProperties props = props(copyContractsTask)
File contractsDslDir = contractsDslDir(copyContractsTask, props)
if (getConfigProperties().getContractDependency()) {
project.logger.debug("Updating the stubs locations for the case where we have a JAR with contracts")
props.contractsDslDir = contractsDslDir
props.includedContracts = ".*"
}
File generatedTestSources = config.generatedTestSourcesDir.get().asFile
File generatedTestResources = config.generatedTestResourcesDir.get().asFile
logger.info("Generated test sources dir [${ generatedTestSources}]")
logger.info("Generated test resources dir [${generatedTestResources}]")
File contractsDslDir = config.contractsDslDir.get().asFile
String includedContracts = config.includedContracts.get()
project.logger.info("Spring Cloud Contract Verifier Plugin: Invoking test sources generation")
project.logger.info("Contracts are unpacked to [${contractsDslDir}]")
project.logger.info("Included contracts are [${props.includedContracts}]")
def sourceSetType = getConfigProperties().getTestFramework() == TestFramework.SPOCK ?
"groovy" : "java"
applySourceSets(sourceSetType)
project.logger.info("Included contracts are [${includedContracts}]")
try {
props = props ?: ExtensionToProperties.fromExtension(getConfigProperties())
props.contractsDslDir = contractsDslDir
TestGenerator generator = new TestGenerator(props)
List<String> excludedFiles = config.excludedFiles.get()
List<String> ignoredFiles = config.ignoredFiles.get()
List<String> includedFiles = config.includedFiles.get()
String[] imports = config.imports.get().toArray(new String[0])
String[] staticImports = config.staticImports.get().toArray(new String[0])
TestGenerator generator = new TestGenerator(new ContractVerifierConfigProperties(
includedContracts: includedContracts,
contractsDslDir: contractsDslDir,
nameSuffixForTests: config.nameSuffixForTests.getOrNull(),
generatedTestSourcesDir: generatedTestSources,
generatedTestResourcesDir: generatedTestResources,
basePackageForTests: config.basePackageForTests.getOrNull(),
baseClassForTests: config.baseClassForTests.getOrNull(),
packageWithBaseClasses: config.packageWithBaseClasses.getOrNull(),
excludedFiles: excludedFiles,
ignoredFiles: ignoredFiles,
includedFiles: includedFiles,
imports: imports,
staticImports: staticImports,
testMode: config.testMode.get(),
testFramework: config.testFramework.get(),
baseClassMappings: config.baseClassMappings.get(),
assertJsonSize: config.assertJsonSize.get(),
failOnInProgress: config.failOnInProgress.get()
))
int generatedClasses = generator.generate()
project.logger.info("Generated {} test classes", generatedClasses)
}
@@ -74,41 +139,28 @@ class GenerateServerTestsTask extends ConventionTask {
}
}
@CompileDynamic
private void applySourceSets(sourceSetType) {
project.sourceSets.test."${sourceSetType}" {
project.logger.
info("Registering ${getConfigProperties().generatedTestSourcesDir} as test source directory")
srcDir getConfigProperties().getGeneratedTestSourcesDir()
}
project.sourceSets.test.resources {
project.logger.
info("Registering ${getConfigProperties().generatedTestResourcesDir} as test resource directory")
srcDir getConfigProperties().getGeneratedTestResourcesDir()
}
}
static Config fromExtension(ContractVerifierExtension extension, TaskProvider<InitContractsTask> initContractsTask,
TaskProvider<ContractsCopyTask> copyContractsTask) {
return new Config(
contractsDslDir: copyContractsTask.flatMap { it.config.copiedContractsFolder },
includedContracts: initContractsTask.flatMap { it.config.includedContracts },
nameSuffixForTests: extension.nameSuffixForTests,
basePackageForTests: extension.basePackageForTests,
baseClassForTests: extension.baseClassForTests,
packageWithBaseClasses: extension.packageWithBaseClasses,
excludedFiles: extension.excludedFiles,
ignoredFiles: extension.ignoredFiles,
includedFiles: extension.includedFiles,
imports: extension.imports,
staticImports: extension.staticImports,
testMode: extension.testMode,
testFramework: extension.testFramework,
baseClassMappings: extension.baseClassMappings,
assertJsonSize: extension.assertJsonSize,
failOnInProgress: extension.failOnInProgress,
@CompileDynamic
private ContractVerifierConfigProperties props(Task task) {
try {
return task.ext.contractVerifierConfigProperties
}
catch (Exception e) {
project.logger.error("Couldn't retrieve the configuration property set by the copy contracts task", e)
ContractVerifierConfigProperties props = ExtensionToProperties.fromExtension(getConfigProperties())
getDownloader().downloadAndUnpackContractsIfRequired(getConfigProperties(), props)
return props
}
}
@CompileDynamic
private File contractsDslDir(Task task, ContractVerifierConfigProperties props) {
try {
return task.ext.contractsDslDir
}
catch (Exception e) {
project.logger.error("Couldn't retrieve the contract dsl property set by the copy contracts task", e)
return props.contractsDslDir
}
generatedTestSourcesDir: extension.generatedTestSourcesDir,
generatedTestResourcesDir: extension.generatedTestResourcesDir,
)
}
}

View File

@@ -1,21 +1,20 @@
package org.springframework.cloud.contract.verifier.plugin
import java.util.concurrent.ConcurrentHashMap
import groovy.transform.CompileStatic
import groovy.transform.Immutable
import groovy.transform.ImmutableOptions
import groovy.transform.PackageScope
import org.gradle.api.Project
import org.gradle.api.logging.Logger
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.StubRunnerOptions
import org.springframework.cloud.contract.stubrunner.StubRunnerOptionsBuilder
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties
import org.springframework.util.StringUtils
import java.util.concurrent.ConcurrentHashMap
/**
* @author Marcin Grzejszczak
*/
@@ -26,83 +25,79 @@ class GradleContractsDownloader {
private static final String LATEST_VERSION = '+'
private final Project project
private final Logger log
private final Logger logger
protected static final Map<StubConfiguration, File> downloadedContract = new ConcurrentHashMap<>()
GradleContractsDownloader(Project project, Logger log) {
GradleContractsDownloader(Project project, Logger logger) {
this.project = project
this.log = log
this.logger = logger
}
File downloadAndUnpackContractsIfRequired(ContractVerifierExtension extension,
ContractVerifierConfigProperties config) {
File defaultContractsDir = extension.contractsDslDir
this.log.info("Project has group id [{}], artifact id [{}]", this.project.group, this.project.name)
DownloadedData downloadAndUnpackContractsIfRequired(ContractVerifierExtension.Dependency contractDependency,
ContractVerifierExtension.ContractRepository contractRepository, String contractsPath,
StubRunnerProperties.StubsMode contractsMode, boolean deleteStubsAfterTest,
Map<String, String> contractsProperties, boolean failOnNoContracts) {
if (!shouldDownloadContracts(contractDependency, contractRepository)) {
return null
}
logger.info("Project has group id [{}], artifact id [{}]", project.group, 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)
this.log.info("The contract dependency is a changing one [{}] and cache download switch is set to [{}]",
configuration.isVersionChanging(), extension.contractRepository.cacheDownloadedContracts)
if (!configuration.isVersionChanging() && extension.contractRepository.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
}
logger.info("For project [${project.name}] Download dependency is provided - will download contract jars")
logger.info("Contract dependency [{}]", contractDependency)
StubConfiguration configuration = stubConfiguration(contractDependency)
logger.info("Got the following contract dependency to download [{}]", configuration)
logger.info("The contract dependency is a changing one [{}] and cache download switch is set to [{}]",
configuration.isVersionChanging(), contractRepository.cacheDownloadedContracts.get())
if (!configuration.isVersionChanging() && contractRepository.cacheDownloadedContracts.get()) {
logger.info("Resolved a non changing version - will try to return the folder from a cache")
File cachedFolder = downloadedContract.get(configuration)
if (cachedFolder) {
logger.info("For project [{}] returning the cached location of the contracts", project.name)
final ContractDownloader.InclusionProperties inclusionProperties =
contractDownloader(configuration, contractRepository, contractsPath, contractsMode,
deleteStubsAfterTest, contractsProperties, failOnNoContracts).createNewInclusionProperties(cachedFolder)
return new DownloadedData(
downloadedContracts: contractsSubDirIfPresent(cachedFolder, logger),
inclusionProperties: inclusionProperties
)
}
File downloadedContracts = contractDownloader(extension, configuration).unpackedDownloadedContracts(config)
downloadedContract.put(configuration, downloadedContracts)
return downloadedContracts
}
this.log.info("For project [{}] will use contracts provided in the folder [{}]", this.project.name, defaultContractsDir)
return defaultContractsDir
final ContractDownloader contractDownloader =
contractDownloader(configuration, contractRepository, contractsPath, contractsMode, deleteStubsAfterTest,
contractsProperties, failOnNoContracts);
final File downloadedContracts = contractDownloader.unpackAndDownloadContracts();
final ContractDownloader.InclusionProperties inclusionProperties =
contractDownloader.createNewInclusionProperties(downloadedContracts)
downloadedContract.put(configuration, downloadedContracts)
return new DownloadedData(
downloadedContracts: contractsSubDirIfPresent(downloadedContracts, logger),
inclusionProperties: inclusionProperties
)
}
private boolean shouldDownloadContracts(ContractVerifierExtension extension) {
return StringUtils.hasText(extension.contractDependency.getArtifactId()) ||
StringUtils.hasText(extension.contractDependency.getStringNotation()) ||
StringUtils.hasText(extension.contractRepository.repositoryUrl)
}
protected ContractDownloader contractDownloader(ContractVerifierExtension extension, StubConfiguration configuration) {
return new ContractDownloader(stubDownloader(extension), configuration,
extension.contractsPath, this.project.group as String, this.project.name, this.project.version as String)
}
protected StubDownloader stubDownloader(ContractVerifierExtension extension) {
StubDownloaderBuilderProvider provider = new StubDownloaderBuilderProvider()
return provider.get(options(extension))
}
protected StubRunnerOptions options(ContractVerifierExtension extension) {
StubRunnerOptionsBuilder options = new StubRunnerOptionsBuilder()
.withOptions(StubRunnerOptions.fromSystemProps())
.withStubRepositoryRoot(extension.contractRepository.repositoryUrl)
.withStubsMode(extension.contractsMode)
.withUsername(extension.contractRepository.username)
.withPassword(extension.contractRepository.password)
.withDeleteStubsAfterTest(extension.deleteStubsAfterTest)
.withProperties(extension.contractsProperties)
if (extension.contractRepository.proxyPort) {
options = options.withProxy(extension.contractRepository.proxyHost, extension.contractRepository.proxyPort)
private static File contractsSubDirIfPresent(File contractsDirectory, Logger logger) {
File contracts = new File(contractsDirectory, "contracts")
if (contracts.exists()) {
if (logger.isDebugEnabled()) {
logger.debug("Contracts folder found [" + contracts + "]")
}
contractsDirectory = contracts
}
options = options.withFailOnNoStubs(extension.failOnNoContracts)
return options.build()
return contractsDirectory
}
@PackageScope
StubConfiguration stubConfiguration(ContractVerifierExtension.Dependency contractDependency) {
String groupId = contractDependency.groupId
String artifactId = contractDependency.artifactId
String version = StringUtils.hasText(contractDependency.version) ?
contractDependency.version : LATEST_VERSION
String classifier = contractDependency.classifier
String stringNotation = contractDependency.stringNotation
String groupId = contractDependency.groupId.getOrNull()
String artifactId = contractDependency.artifactId.getOrNull()
String version = StringUtils.hasText(contractDependency.version.getOrNull()) ?
contractDependency.version.getOrNull() : LATEST_VERSION
String classifier = contractDependency.classifier.getOrNull()
String stringNotation = contractDependency.stringNotation.getOrNull()
if (StringUtils.hasText(stringNotation)) {
StubConfiguration stubConfiguration = new StubConfiguration(stringNotation)
return new StubConfiguration(stubConfiguration.groupId, stubConfiguration.artifactId,
@@ -110,4 +105,35 @@ class GradleContractsDownloader {
}
return new StubConfiguration(groupId, artifactId, version, classifier)
}
protected ContractDownloader contractDownloader(StubConfiguration configuration,
ContractVerifierExtension.ContractRepository contractRepository,
String contractsPath, StubRunnerProperties.StubsMode contractsMode,
boolean deleteStubsAfterTest, Map<String, String> contractsProperties,
boolean failOnNoContracts) {
return new ContractDownloader(stubDownloader(contractRepository, contractsMode, deleteStubsAfterTest, contractsProperties, failOnNoContracts),
configuration, contractsPath, project.group as String, project.name, project.version as String)
}
protected StubDownloader stubDownloader(ContractVerifierExtension.ContractRepository contractRepository,
StubRunnerProperties.StubsMode contractsMode, boolean deleteStubsAfterTest,
Map<String, String> contractsProperties, boolean failOnNoContracts) {
StubDownloaderBuilderProvider provider = new StubDownloaderBuilderProvider()
return provider.get(StubRunnerOptionsFactory.createStubRunnerOptions(contractRepository, contractsMode,
deleteStubsAfterTest, contractsProperties, failOnNoContracts))
}
private static boolean shouldDownloadContracts(ContractVerifierExtension.Dependency contractDependency,
ContractVerifierExtension.ContractRepository contractRepository) {
return StringUtils.hasText(contractDependency.getArtifactId().getOrNull()) ||
StringUtils.hasText(contractDependency.getStringNotation().getOrNull()) ||
StringUtils.hasText(contractRepository.repositoryUrl.getOrNull())
}
@ImmutableOptions(knownImmutableClasses = [File, ContractDownloader.InclusionProperties])
@Immutable
static class DownloadedData {
final File downloadedContracts
final ContractDownloader.InclusionProperties inclusionProperties
}
}

View File

@@ -0,0 +1,90 @@
package org.springframework.cloud.contract.verifier.plugin
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties
/**
* @author Anatoliy Balakirev
*/
@PackageScope
@CompileStatic
class InitContractsTask extends DefaultTask {
static final String TASK_NAME = 'initContracts'
protected final GradleContractsDownloader contractDownloader = new GradleContractsDownloader(project, logger)
@Nested
Config config
static class Config {
// All fields inside `@Nested` one are properly marked as an `@Input` to work with incremental build:
@Nested
@Optional
ContractVerifierExtension.Dependency contractDependency
@Nested
@Optional
ContractVerifierExtension.ContractRepository contractRepository
@Input
@Optional
Property<StubRunnerProperties.StubsMode> contractsMode
@Input
Property<Boolean> deleteStubsAfterTest
@Input
Property<Boolean> failOnNoContracts
@Input
MapProperty<String, String> contractsProperties
@Input
@Optional
Property<String> contractsPath
@Internal
Property<String> includedContracts
@Internal
Property<String> includedRootFolderAntPattern
@OutputDirectory
DirectoryProperty initialisedContractsDirectory
}
@TaskAction
void initContracts() {
GradleContractsDownloader.DownloadedData downloaded =
contractDownloader.downloadAndUnpackContractsIfRequired(config.contractDependency,
config.contractRepository, config.contractsPath.getOrNull(), config.contractsMode.getOrNull(),
config.deleteStubsAfterTest.get(), config.contractsProperties.get(), config.failOnNoContracts.get())
if (downloaded) {
config.includedContracts.set(downloaded.inclusionProperties.includedContracts)
config.includedRootFolderAntPattern.set(downloaded.inclusionProperties.includedRootFolderAntPattern)
config.initialisedContractsDirectory.set(downloaded.downloadedContracts)
}
logger.info("For project [{}] will use contracts provided in the folder [{}]", project.name, config.initialisedContractsDirectory.get())
}
static Config fromExtension(ContractVerifierExtension extension, Project project) {
DirectoryProperty initialisedContractsDirectory = project.objects.directoryProperty()
initialisedContractsDirectory.set(extension.contractsDslDir)
return new Config(
contractDependency: extension.contractDependency,
contractRepository: extension.contractRepository,
contractsMode: extension.contractsMode,
deleteStubsAfterTest: extension.deleteStubsAfterTest,
failOnNoContracts: extension.failOnNoContracts,
contractsProperties: extension.contractsProperties,
contractsPath: extension.contractsPath,
includedContracts: project.objects.property(String).convention(".*"),
includedRootFolderAntPattern: project.objects.property(String).convention("**/"),
initialisedContractsDirectory: initialisedContractsDirectory
)
}
}

View File

@@ -1,29 +0,0 @@
package org.springframework.cloud.contract.verifier.plugin
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.gradle.api.Project
/**
* @author Marcin Grzejszczak
*/
@CompileStatic
@PackageScope
class OutputFolderBuilder {
private static final String DEFAULT_MAPPINGS_FOLDER = 'mappings'
static String buildRootPath(Project project) {
String groupId = project.group as String
String artifactId = project.name
String version = project.version
return "META-INF/${groupId}/${artifactId}/${version}"
}
static File outputMappingsDir(Project project, File stubsOutputDir) {
String root = OutputFolderBuilder.buildRootPath(project)
return stubsOutputDir != null ?
new File(stubsOutputDir, "${root}/${DEFAULT_MAPPINGS_FOLDER}")
: new File(project.buildDir, "stubs/${root}/${DEFAULT_MAPPINGS_FOLDER}")
}
}

View File

@@ -17,12 +17,19 @@
package org.springframework.cloud.contract.verifier.plugin
import groovy.transform.CompileStatic
import org.gradle.api.internal.ConventionTask
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.springframework.cloud.contract.stubrunner.ContractProjectUpdater
import org.springframework.cloud.contract.stubrunner.ScmStubDownloaderBuilder
import org.springframework.cloud.contract.stubrunner.StubRunnerOptions
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties
/**
* For SCM based repositories will copy the generated stubs
@@ -30,49 +37,66 @@ import org.springframework.cloud.contract.stubrunner.StubRunnerOptions
* commit the changes and push them to origin.
*
* @author Marcin Grzejszczak
* @author Anatoliy Balakirev
* @since 2.0.0
*/
@CompileStatic
class PublishStubsToScmTask extends ConventionTask {
File stubsOutputDir
ContractVerifierExtension configProperties
GradleContractsDownloader downloader
private final ExtensionHolderSpec closureHolder = new ClosureHolder()
class PublishStubsToScmTask extends DefaultTask {
static final String TASK_NAME = 'publishStubsToScm'
@Nested
Config config
static class Config {
// All fields inside `@Nested` one are properly marked as an `@Input` to work with incremental build:
@Nested
@Optional
ContractVerifierExtension.ContractRepository contractRepository
@Input
@Optional
Provider<StubRunnerProperties.StubsMode> contractsMode
@Input
Provider<Boolean> deleteStubsAfterTest
@Input
Provider<Boolean> failOnNoContracts
@Input
MapProperty<String, String> contractsProperties
@OutputDirectory
DirectoryProperty stubsOutputDir
}
@TaskAction
void publishStubsToScm() {
ContractVerifierExtension clonedExtension = modifyExtension()
if (!shouldRun(clonedExtension)) {
if (!shouldRun()) {
return
}
String projectName = project.group.toString() + ":" + project.name.toString() + ":" + this.project.version.toString()
project.logger.info("Pushing Stubs to SCM for project [" + projectName + "]")
StubRunnerOptions options = getDownloader().options(clonedExtension)
new ContractProjectUpdater(options).updateContractProject(projectName, getStubsOutputDir().toPath())
StubRunnerOptions stubRunnerOptions = StubRunnerOptionsFactory.createStubRunnerOptions(
config.contractRepository, config.contractsMode.getOrNull(), config.deleteStubsAfterTest.get(),
config.contractsProperties.get(), config.failOnNoContracts.get())
new ContractProjectUpdater(stubRunnerOptions).updateContractProject(projectName, config.stubsOutputDir.get().asFile.toPath())
}
private ContractVerifierExtension modifyExtension() {
ContractVerifierExtension clone = getConfigProperties().copy()
this.closureHolder.extensionClosure.delegate = clone
this.closureHolder.extensionClosure.call(clone)
return clone
static Config fromExtension(ContractVerifierExtension extension) {
return new Config(
contractRepository: extension.contractRepository,
contractsMode: extension.contractsMode,
failOnNoContracts: extension.failOnNoContracts,
deleteStubsAfterTest: extension.deleteStubsAfterTest,
contractsProperties: extension.contractsProperties,
stubsOutputDir: extension.stubsOutputDir
)
}
private boolean shouldRun(ContractVerifierExtension clonedExtension) {
String contractRepoUrl = clonedExtension.contractRepository.repositoryUrl ?: ""
private boolean shouldRun() {
String contractRepoUrl = config.contractRepository.repositoryUrl.getOrNull() ?: ""
if (!contractRepoUrl || !ScmStubDownloaderBuilder.isProtocolAccepted(contractRepoUrl)) {
project.logger.warn("Skipping pushing stubs to scm since your contracts repository URL [${contractRepoUrl}] doesn't match any of the accepted protocols for SCM stub downloader")
return false
}
return true
}
void customize(@DelegatesTo(ContractVerifierExtension) Closure closure) {
project.logger.debug("Storing the extension closure")
this.closureHolder.extensionClosure = closure
}
private static class ClosureHolder implements ExtensionHolderSpec {
Closure extensionClosure = Closure.IDENTITY
}
}

View File

@@ -24,7 +24,10 @@ import org.gradle.api.Task
import org.gradle.api.plugins.GroovyPlugin
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin
import org.gradle.api.tasks.TaskProvider
import org.gradle.jvm.tasks.Jar
import org.springframework.cloud.contract.verifier.config.TestFramework
/**
* Gradle plugin for Spring Cloud Contract Verifier that from the DSL contract can
* <ul>
@@ -34,18 +37,14 @@ import org.gradle.jvm.tasks.Jar
*
* @author Jakub Kubrynski, codearte.io
* @author Marcin Grzejszczak
* @author Anatoliy Balakirev
*
* @since 1.0.0
*/
@CompileStatic
class SpringCloudContractVerifierGradlePlugin implements Plugin<Project> {
protected static final String GENERATE_SERVER_TESTS_TASK_NAME = 'generateContractTests'
protected static final String DSL_TO_CLIENT_TASK_NAME = 'generateClientStubs'
protected static final String COPY_CONTRACTS_TASK_NAME = 'copyContracts'
protected static final String VERIFIER_STUBS_JAR_TASK_NAME = 'verifierStubsJar'
protected static final String PUBLISH_STUBS_TO_SCM_TASK_NAME = 'publishStubsToScm'
private static final String VERIFIER_STUBS_JAR_TASK_NAME = 'verifierStubsJar'
private static final String GROUP_NAME = "Verification"
private static final String EXTENSION_NAME = 'contracts'
@@ -56,33 +55,48 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin<Project> {
this.project = project
project.plugins.apply(GroovyPlugin)
ContractVerifierExtension extension = project.extensions.create(EXTENSION_NAME, ContractVerifierExtension)
GradleContractsDownloader downloader = new GradleContractsDownloader(this.project, this.project.logger)
addDependsOn(project)
setConfigurationDefaults(extension)
Task stubsJar = createAndConfigureStubsJarTasks(extension)
Task copyContracts = createAndConfigureCopyContractsTask(stubsJar, downloader, extension)
createAndConfigureMavenPublishPlugin(stubsJar, extension)
createGenerateTestsTask(extension, copyContracts, downloader)
createAndConfigureGenerateClientStubs(extension, copyContracts)
createAndConfigurePublishStubsToScmTask(extension, downloader)
addIdeaTestSources(project, extension)
TaskProvider<InitContractsTask> initContracts = createAndConfigureContractsInitTask(extension)
TaskProvider<ContractsCopyTask> copyContracts = createAndConfigureCopyContractsTask(initContracts, extension)
TaskProvider<GenerateClientStubsFromDslTask> generateClientStubs = createAndConfigureGenerateClientStubs(extension, copyContracts)
createAndConfigureStubsJarTasks(extension, copyContracts, generateClientStubs)
createGenerateTestsTask(extension, copyContracts, initContracts)
createAndConfigurePublishStubsToScmTask(extension, generateClientStubs)
project.afterEvaluate {
addIdeaTestSources(project, extension)
applyDefaultSourceSets(extension)
}
}
// This must be called within afterEvaluate due to getting data from extension, which must be initialised first:
@CompileDynamic
private void addDependsOn(Project project) {
project.check.dependsOn(GENERATE_SERVER_TESTS_TASK_NAME)
private void applyDefaultSourceSets(ContractVerifierExtension extension) {
String sourceSetType = extension.testFramework.get() == TestFramework.SPOCK ? "groovy" : "java"
project.sourceSets.test."${sourceSetType}" {
project.logger.
info("Registering ${extension.generatedTestSourcesDir.get().asFile} as test source directory")
srcDir extension.generatedTestSourcesDir.get().asFile
}
project.sourceSets.test.resources {
project.logger.
info("Registering ${extension.generatedTestResourcesDir.get().asFile} as test resource directory")
srcDir extension.generatedTestResourcesDir.get().asFile
}
}
// This must be called within afterEvaluate due to getting data from extension, which must be initialised first:
@CompileDynamic
private addIdeaTestSources(Project project, ContractVerifierExtension extension) {
def hasIdea = new File(project.rootDir, ".idea").exists()
boolean hasIdea = new File(project.rootDir, ".idea").exists()
if (hasIdea) {
project.apply(plugin: 'idea')
project.idea {
module {
testSourceDirs += extension.generatedTestSourcesDir
testSourceDirs += extension.generatedTestResourcesDir
testSourceDirs += extension.contractsDslDir
testSourceDirs += extension.generatedTestSourcesDir.get().asFile
testSourceDirs += extension.generatedTestResourcesDir.get().asFile
testSourceDirs += extension.contractsDslDir.get().asFile
}
}
}
@@ -90,92 +104,108 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin<Project> {
private void setConfigurationDefaults(ContractVerifierExtension extension) {
extension.with {
generatedTestSourcesDir = generatedTestSourcesDir ?: project.file("${project.buildDir}/generated-test-sources/contracts")
generatedTestSourcesDir.mkdirs()
generatedTestResourcesDir = generatedTestResourcesDir ?: project.file("${project.buildDir}/generated-test-resources/contracts")
generatedTestResourcesDir.mkdirs()
contractsDslDir = contractsDslDir ?: defaultContractsDir() //TODO: Use sourceset
contractsDslDir.mkdirs()
stubsOutputDir = stubsOutputDir ?: project.file("${project.buildDir}/stubs/")
stubsOutputDir.mkdirs()
it.contractsDslDir.convention(project.layout.projectDirectory.dir("src/test/resources/contracts"))
it.generatedTestSourcesDir.convention(project.layout.getBuildDirectory().dir("generated-test-sources/contracts"))
it.generatedTestResourcesDir.convention(project.layout.getBuildDirectory().dir("generated-test-resources/contracts"))
it.stubsOutputDir.convention(project.layout.getBuildDirectory().dir("stubs"))
}
}
private File defaultContractsDir() {
return project.file("${project.projectDir}/src/test/resources/contracts")
}
@CompileDynamic
private void createGenerateTestsTask(ContractVerifierExtension extension, Task copyContracts,
GradleContractsDownloader gradleContractsDownloader) {
Task task = project.tasks.create(GENERATE_SERVER_TESTS_TASK_NAME, GenerateServerTestsTask)
task.description = "Generate server tests from the contracts"
task.group = GROUP_NAME
task.conventionMapping.with {
downloader = { gradleContractsDownloader }
generatedTestSourcesDir = { extension.generatedTestSourcesDir }
configProperties = { extension }
private void createGenerateTestsTask(ContractVerifierExtension extension,
TaskProvider<ContractsCopyTask> copyContracts, TaskProvider<InitContractsTask> initContracts) {
TaskProvider<GenerateServerTestsTask> task = project.tasks.register(GenerateServerTestsTask.TASK_NAME, GenerateServerTestsTask)
task.configure {
it.description = "Generate server tests from the contracts"
it.group = GROUP_NAME
it.enabled = !project.gradle.startParameter.excludedTaskNames.contains("test")
it.config = GenerateServerTestsTask.fromExtension(extension, initContracts, copyContracts)
it.dependsOn copyContracts
}
task.enabled = !project.gradle.startParameter.excludedTaskNames.contains("test")
task.dependsOn copyContracts
project.tasks.findByName("compileTestJava").dependsOn(task)
project.tasks.findByName("check").dependsOn(task)
}
@CompileDynamic
private void createAndConfigurePublishStubsToScmTask(ContractVerifierExtension extension,
GradleContractsDownloader gradleContractsDownloader) {
Task task = project.tasks.create(PUBLISH_STUBS_TO_SCM_TASK_NAME, PublishStubsToScmTask)
task.description = "The generated stubs get committed to the SCM repo and pushed to origin"
task.group = GROUP_NAME
task.conventionMapping.with {
downloader = { gradleContractsDownloader }
configProperties = { extension }
stubsOutputDir = { extension.stubsOutputDir }
TaskProvider<GenerateClientStubsFromDslTask> generateClientStubs) {
TaskProvider<PublishStubsToScmTask> task = project.tasks.register(PublishStubsToScmTask.TASK_NAME, PublishStubsToScmTask)
task.configure {
it.description = "The generated stubs get committed to the SCM repo and pushed to origin"
it.group = GROUP_NAME
it.config = PublishStubsToScmTask.fromExtension(extension)
it.dependsOn generateClientStubs
}
task.dependsOn DSL_TO_CLIENT_TASK_NAME
}
@CompileDynamic
private Task createAndConfigureGenerateClientStubs(ContractVerifierExtension extension,
Task copyContracts) {
Task task = project.tasks.create(DSL_TO_CLIENT_TASK_NAME, GenerateClientStubsFromDslTask)
task.description = "Generate client stubs from the contracts"
task.group = GROUP_NAME
task.conventionMapping.with {
downloader = { gradleContractsDownloader }
stubsOutputDir = { extension.stubsOutputDir }
configProperties = { extension }
private TaskProvider<GenerateClientStubsFromDslTask> createAndConfigureGenerateClientStubs(ContractVerifierExtension extension,
TaskProvider<ContractsCopyTask> copyContracts) {
TaskProvider<GenerateClientStubsFromDslTask> task = project.tasks.register(GenerateClientStubsFromDslTask.TASK_NAME, GenerateClientStubsFromDslTask)
task.configure {
it.description = "Generate client stubs from the contracts"
it.group = GROUP_NAME
it.config = GenerateClientStubsFromDslTask.fromExtension(extension, copyContracts, buildRootPath(), project)
it.dependsOn copyContracts
}
task.dependsOn copyContracts
return task
}
@CompileDynamic
private Task createAndConfigureStubsJarTasks(ContractVerifierExtension extension) {
Task task = stubsTask()
private TaskProvider<Task> createAndConfigureStubsJarTasks(ContractVerifierExtension extension,
TaskProvider<ContractsCopyTask> copyContracts,
TaskProvider<GenerateClientStubsFromDslTask> generateClientStubs) {
TaskProvider<Task> task = stubsTask()
if (task) {
// How is this possible? Where can it come from?
project.logger.info("Spring Cloud Contract Verifier Plugin: Stubs jar task was present - won't create one. Remember about adding it to artifacts as an archive!")
return task
} else {
task = createStubsJarTask(extension, generateClientStubs)
}
else {
task = project.tasks.create(type: Jar, name: VERIFIER_STUBS_JAR_TASK_NAME,
dependsOn: DSL_TO_CLIENT_TASK_NAME) {
baseName = project.name
classifier = extension.stubsSuffix
from { extension.stubsOutputDir ?: project.file("${project.buildDir}/stubs") }
task.configure {
it.dependsOn copyContracts
}
createAndConfigureMavenPublishPlugin(task, extension)
return task
}
private void createAndConfigureMavenPublishPlugin(TaskProvider<Task> stubsTask, ContractVerifierExtension extension) {
if (!classIsOnClasspath("org.gradle.api.publish.maven.plugins.MavenPublishPlugin")) {
project.logger.debug("Maven Publish Plugin is not present - won't add default publication")
return
}
// This must be called within afterEvaluate due to getting data from extension, which must be initialised first:
project.afterEvaluate {
project.logger.debug("Spring Cloud Contract Verifier Plugin: Generating default publication")
if (extension.disableStubPublication.get()) {
project.logger.info("You've switched off the stub publication - won't add default publication")
return
}
task.description = "Creates the stubs JAR task"
task.group = GROUP_NAME
project.artifacts {
archives task
project.plugins.withType(MavenPublishPlugin) { def publishingPlugin ->
def publishingExtension = project.extensions.findByName('publishing')
if (hasStubsPublication(publishingExtension)) {
project.logger.info("Spring Cloud Contract Verifier Plugin: Stubs publication was present - won't create a new one. Remember about passing stubs as artifact")
} else {
project.logger.debug("Spring Cloud Contract Verifier Plugin: Stubs publication is not present - will create one")
setPublications(publishingExtension, stubsTask)
}
}
return task
}
}
private Task stubsTask() {
@CompileDynamic
private void setPublications(def publishingExtension, TaskProvider<Task> stubsTask) {
publishingExtension.publications {
stubs(MavenPublication) {
artifactId "${project.name}"
artifact stubsTask.get() // TODO: How to make it lazily initialised?
}
}
}
private TaskProvider<Task> stubsTask() {
try {
return project.tasks.getByName(VERIFIER_STUBS_JAR_TASK_NAME)
return project.tasks.named(VERIFIER_STUBS_JAR_TASK_NAME)
}
catch (Exception e) {
return null
@@ -183,52 +213,7 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin<Project> {
}
@CompileDynamic
private Task createAndConfigureCopyContractsTask(Task stubs,
GradleContractsDownloader gradleContractsDownloader,
ContractVerifierExtension contractVerifierExtension) {
Task task = project.tasks.create(COPY_CONTRACTS_TASK_NAME, ContractsCopyTask)
task.description = "Copies contracts to the output folder"
task.group = GROUP_NAME
task.conventionMapping.with {
downloader = { gradleContractsDownloader }
extension = { contractVerifierExtension }
}
stubs.dependsOn task
return task
}
@CompileDynamic
private void createAndConfigureMavenPublishPlugin(Task stubsTask, ContractVerifierExtension extension) {
if (!classIsOnClasspath("org.gradle.api.publish.maven.plugins.MavenPublishPlugin")) {
project.logger.debug("Maven Publish Plugin is not present - won't add default publication")
return
}
project.logger.debug("Spring Cloud Contract Verifier Plugin: Generating default publication")
project.afterEvaluate {
if (extension.isDisableStubPublication()) {
project.logger.info("You've switched off the stub publication - won't add default publication")
return
}
project.plugins.withType(MavenPublishPlugin) { def publishingPlugin ->
def publishingExtension = project.extensions.findByName('publishing')
if (!hasPublication(publishingExtension)) {
project.logger.debug("Spring Cloud Contract Verifier Plugin: Stubs publication is not present - will create one")
publishingExtension.publications {
stubs(MavenPublication) {
artifactId "${project.name}"
artifact stubsTask
}
}
}
else {
project.logger.info("Spring Cloud Contract Verifier Plugin: Stubs publication was present - won't create a new one. Remember about passing stubs as artifact")
}
}
}
}
@CompileDynamic
private boolean hasPublication(def publishingExtension) {
private boolean hasStubsPublication(def publishingExtension) {
try {
return publishingExtension.publications.getByName('stubs')
}
@@ -237,6 +222,49 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin<Project> {
}
}
// TODO: Can we define inputs / outputs and make it incremental?
@CompileDynamic
private TaskProvider<Task> createStubsJarTask(ContractVerifierExtension extension,
TaskProvider<GenerateClientStubsFromDslTask> generateClientStubs) {
TaskProvider<Jar> task = project.tasks.register(VERIFIER_STUBS_JAR_TASK_NAME, Jar)
task.configure {
it.description = "Creates the stubs JAR task"
it.group = GROUP_NAME
it.getArchiveBaseName().set(project.name)
it.getArchiveClassifier().set(extension.stubsSuffix)
it.from { extension.stubsOutputDir }
it.dependsOn generateClientStubs
}
project.artifacts {
archives task
}
return task
}
private TaskProvider<ContractsCopyTask> createAndConfigureCopyContractsTask(TaskProvider<InitContractsTask> initContractsTask,
ContractVerifierExtension extension) {
TaskProvider<ContractsCopyTask> task = project.tasks.register(ContractsCopyTask.TASK_NAME, ContractsCopyTask)
task.configure {
it.description = "Copies contracts to the output folder"
it.group = GROUP_NAME
it.config = ContractsCopyTask.fromExtension(extension, initContractsTask, buildRootPath(), project)
it.dependsOn initContractsTask
}
return task
}
private TaskProvider<InitContractsTask> createAndConfigureContractsInitTask(ContractVerifierExtension extension) {
TaskProvider<InitContractsTask> task = project.tasks.register(InitContractsTask.TASK_NAME, InitContractsTask)
task.configure {
it.description = "Initialises contracts either downloading or using default provided folder"
it.group = GROUP_NAME
it.config = InitContractsTask.fromExtension(extension, project)
}
return task
}
private boolean classIsOnClasspath(String className) {
try {
Class.forName(className)
@@ -247,4 +275,11 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin<Project> {
}
return false
}
private String buildRootPath() {
String groupId = project.group as String
String artifactId = project.name
String version = project.version
return "META-INF/${groupId}/${artifactId}/${version}"
}
}

View File

@@ -0,0 +1,35 @@
package org.springframework.cloud.contract.verifier.plugin
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.springframework.cloud.contract.stubrunner.StubRunnerOptions
import org.springframework.cloud.contract.stubrunner.StubRunnerOptionsBuilder
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties
/**
* Helper class to create StubRunnerOptions.
*
* @author Anatoliy Balakirev
*/
@CompileStatic
@PackageScope
class StubRunnerOptionsFactory {
static StubRunnerOptions createStubRunnerOptions(ContractVerifierExtension.ContractRepository contractRepository,
StubRunnerProperties.StubsMode contractsMode, boolean deleteStubsAfterTest,
Map<String, String> contractsProperties, boolean failOnNoContracts) {
StubRunnerOptionsBuilder options = new StubRunnerOptionsBuilder()
.withOptions(StubRunnerOptions.fromSystemProps())
.withStubRepositoryRoot(contractRepository.repositoryUrl.getOrNull())
.withStubsMode(contractsMode)
.withUsername(contractRepository.username.getOrNull())
.withPassword(contractRepository.password.getOrNull())
.withDeleteStubsAfterTest(deleteStubsAfterTest)
.withProperties(contractsProperties)
.withFailOnNoStubs(failOnNoContracts)
if (contractRepository.proxyPort.getOrNull()) {
options = options.withProxy(contractRepository.proxyHost.getOrNull(), contractRepository.proxyPort.getOrNull())
}
return options.build()
}
}

View File

@@ -1,153 +0,0 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.springframework.cloud.contract.verifier.plugin
import spock.lang.Specification
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties
import org.springframework.cloud.contract.verifier.config.TestFramework
import org.springframework.cloud.contract.verifier.config.TestMode
class ContractVerifierExtensionSpec extends Specification {
def "should make a copy of the object"() {
given:
ContractVerifierExtension original = new ContractVerifierExtension(
testFramework: TestFramework.JUNIT5,
testMode: TestMode.EXPLICIT,
basePackageForTests: "foo1",
baseClassForTests: "foo2",
nameSuffixForTests: "foo3",
ruleClassForTests: "foo4",
excludedFiles: ["foo5"],
includedFiles: ["foo6"],
ignoredFiles: ["foo7"],
imports: ["foo8"],
staticImports: ["foo9"],
contractsDslDir: new File("foo10"),
generatedTestSourcesDir: new File("foo11"),
generatedTestResourcesDir: new File("foo12"),
stubsOutputDir: new File("foo13"),
stubsSuffix: 'foo14',
assertJsonSize: false,
contractRepository: new ContractVerifierExtension.ContractRepository(
repositoryUrl: "foo15",
username: "foo16",
password: "foo17",
proxyPort: 18,
proxyHost: "foo19",
cacheDownloadedContracts: false
),
contractDependency: new ContractVerifierExtension.Dependency(
groupId: "foo20",
artifactId: "foo21",
classifier: "foo22",
version: "foo23",
stringNotation: "foo24"
),
contractsPath: "foo25",
contractsMode: StubRunnerProperties.StubsMode.CLASSPATH,
packageWithBaseClasses: "foo26",
baseClassMappings: [foo27: "foo28"],
excludeBuildFolders: false,
deleteStubsAfterTest: false,
convertToYaml: false,
contractsProperties: [foo29: "foo30"]
)
when:
ContractVerifierExtension copy = original.copy()
original.with {
testFramework = TestFramework.CUSTOM
testMode = TestMode.MOCKMVC
basePackageForTests = "bar1"
baseClassForTests = "bar2"
nameSuffixForTests = "bar3"
ruleClassForTests = "bar4"
excludedFiles = ["bar5"]
includedFiles = ["bar6"]
ignoredFiles = ["bar7"]
imports = ["bar8"]
staticImports = ["bar9"]
contractsDslDir = new File("bar10")
generatedTestSourcesDir = new File("bar11")
generatedTestResourcesDir = new File("bar12")
stubsOutputDir = new File("bar13")
stubsSuffix = 'bar14'
assertJsonSize = true
contractRepository.with {
repositoryUrl = "bar15"
username = "bar16"
password = "bar17"
proxyPort = 28
proxyHost = "bar19"
cacheDownloadedContracts = true
}
contractDependency.with {
groupId = "bar20"
artifactId = "bar21"
classifier = "bar22"
version = "bar23"
stringNotation = "bar24"
}
contractsPath = "bar25"
contractsMode = StubRunnerProperties.StubsMode.REMOTE
packageWithBaseClasses = "bar26"
baseClassMappings = [bar27: "bar28"]
excludeBuildFolders = true
deleteStubsAfterTest = true
convertToYaml = true
contractsProperties = [bar29: "bar30"]
}
then:
copy.testFramework == TestFramework.JUNIT5
copy.testMode == TestMode.EXPLICIT
copy.basePackageForTests == "foo1"
copy.baseClassForTests == "foo2"
copy.nameSuffixForTests == "foo3"
copy.ruleClassForTests == "foo4"
copy.excludedFiles == ["foo5"]
copy.includedFiles == ["foo6"]
copy.ignoredFiles == ["foo7"]
copy.imports == ["foo8"]
copy.staticImports == ["foo9"]
copy.contractsDslDir == new File("foo10")
copy.generatedTestSourcesDir == new File("foo11")
copy.generatedTestResourcesDir == new File("foo12")
copy.stubsOutputDir == new File("foo13")
copy.stubsSuffix == 'foo14'
copy.assertJsonSize == false
copy.contractRepository.repositoryUrl == "foo15"
copy.contractRepository.username == "foo16"
copy.contractRepository.password == "foo17"
copy.contractRepository.proxyPort == 18
copy.contractRepository.proxyHost == "foo19"
copy.contractRepository.cacheDownloadedContracts == false
copy.contractDependency.groupId == "foo20"
copy.contractDependency.artifactId == "foo21"
copy.contractDependency.classifier == "foo22"
copy.contractDependency.version == "foo23"
copy.contractDependency.stringNotation == "foo24"
copy.contractsPath == "foo25"
copy.contractsMode == StubRunnerProperties.StubsMode.CLASSPATH
copy.packageWithBaseClasses == "foo26"
copy.baseClassMappings == [foo27: "foo28"]
copy.excludeBuildFolders == false
copy.deleteStubsAfterTest == false
copy.convertToYaml == false
copy.contractsProperties == [foo29: "foo30"]
}
}

View File

@@ -1,8 +1,9 @@
package org.springframework.cloud.contract.verifier.plugin
import org.gradle.api.Task
import org.gradle.api.internal.project.DefaultProject
import org.gradle.api.model.ObjectFactory
import org.gradle.api.plugins.GroovyPlugin
import org.gradle.api.publish.PublicationContainer
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin
import org.gradle.testfixtures.ProjectBuilder
@@ -12,92 +13,71 @@ class ContractVerifierSpec extends Specification {
DefaultProject project
def setup() {
def dateString = new Date().format("yyyy-MM-dd_HH-mm-ss")
def testFolder = new File("build/generated-tests/${getClass().simpleName}/${dateString}")
String dateString = new Date().format("yyyy-MM-dd_HH-mm-ss")
File testFolder = new File("build/generated-tests/${getClass().simpleName}/${dateString}")
testFolder.mkdirs()
project = (DefaultProject) ProjectBuilder.builder().withProjectDir(testFolder).build()
project.plugins.apply(SpringCloudContractVerifierGradlePlugin)
}
def "should apply groovy plugin"() {
given:
project.plugins.apply(SpringCloudContractVerifierGradlePlugin)
expect:
project.plugins.hasPlugin(GroovyPlugin)
}
def "should create contracts extension"() {
given:
project.plugins.apply(SpringCloudContractVerifierGradlePlugin)
expect:
project.extensions.findByType(ContractVerifierExtension) != null
}
def "should create generateContractTests task"() {
given:
project.plugins.apply(SpringCloudContractVerifierGradlePlugin)
expect:
project.tasks.findByName("generateContractTests") != null
project.tasks.named("generateContractTests") != null
}
def "should configure generateContractTests task as a dependency of the check task"() {
given:
project.plugins.apply(SpringCloudContractVerifierGradlePlugin)
expect:
project.tasks.check.getDependsOn().contains("generateContractTests")
project.tasks.check.getDependsOn().contains(project.tasks.named("generateContractTests"))
}
def "should create generateClientStubs task"() {
given:
project.plugins.apply(SpringCloudContractVerifierGradlePlugin)
expect:
project.tasks.findByName("generateClientStubs") != null
project.tasks.named("generateClientStubs") != null
}
def "should create verifierStubsJar task"() {
given:
project.plugins.apply(SpringCloudContractVerifierGradlePlugin)
expect:
project.tasks.findByName("verifierStubsJar") != null
project.tasks.named("verifierStubsJar") != null
}
def "should configure generateClientStubs task as a dependency of the verifierStubsJar task"() {
given:
project.plugins.apply(SpringCloudContractVerifierGradlePlugin)
expect:
project.tasks.verifierStubsJar.getDependsOn().contains("generateClientStubs")
project.tasks.verifierStubsJar.getDependsOn().contains(project.tasks.named("generateClientStubs"))
}
def "should configure generateClientStubs task as a dependency of the publishStubsToScm task"() {
given:
project.plugins.apply(SpringCloudContractVerifierGradlePlugin)
expect:
project.tasks.publishStubsToScm.getDependsOn().contains("generateClientStubs")
project.tasks.publishStubsToScm.getDependsOn().contains(project.tasks.named("generateClientStubs"))
}
def "should create copyContracts task"() {
given:
project.plugins.apply(SpringCloudContractVerifierGradlePlugin)
expect:
project.tasks.findByName("copyContracts") != null
project.tasks.named("copyContracts") != null
}
def "should configure copyContracts task as a dependency of the verifierStubsJar task"() {
given:
project.plugins.apply(SpringCloudContractVerifierGradlePlugin)
Task copyContracts = project.tasks.copyContracts
assert copyContracts != null
expect:
project.tasks.verifierStubsJar.getDependsOn().contains(copyContracts)
project.tasks.verifierStubsJar.getDependsOn().contains(project.tasks.named("copyContracts"))
}
def "should create initContracts task"() {
expect:
project.tasks.named("initContracts") != null
}
def "should configure initContracts task as a dependency of the copyContracts task"() {
expect:
project.tasks.copyContracts.getDependsOn().contains(project.tasks.named("initContracts"))
}
/**
@@ -112,22 +92,23 @@ class ContractVerifierSpec extends Specification {
project.evaluate() // Currently internal method to trigger afterEvaluate blocks.
expect:
def publications = project.extensions.getByType(PublishingExtension).publications
PublicationContainer publications = project.extensions.getByType(PublishingExtension).publications
publications.size() > 0
publications.findByName("stubs") != null
publications.named("stubs") != null
}
def "should compile"() {
given:
ContractVerifierExtension extension = new ContractVerifierExtension()
ObjectFactory objectFactory = Stub(ObjectFactory)
ContractVerifierExtension extension = new ContractVerifierExtension(objectFactory)
extension.with {
// tag::package_with_base_classes[]
packageWithBaseClasses = 'com.example.base'
packageWithBaseClasses.set('com.example.base')
// end::package_with_base_classes[]
// tag::base_class_mappings[]
baseClassForTests = "com.example.FooBase"
baseClassForTests.set("com.example.FooBase")
baseClassMappings {
baseClassMapping('.*/com/.*', 'com.example.ComBase')
baseClassMapping('.*/bar/.*': 'com.example.BarBase')

View File

@@ -1,27 +1,38 @@
package org.springframework.cloud.contract.verifier.plugin
import org.gradle.api.Project
import org.gradle.api.internal.provider.DefaultPropertyState
import org.gradle.api.logging.Logger
import spock.lang.Specification
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
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.spring.StubRunnerProperties
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import spock.lang.Specification
/**
* @author Marcin Grzejszczak
* @author Anatoliy Balakirev
*/
class GradleContractsDownloaderSpec extends Specification {
Project project = Stub(Project)
Logger logger = Stub(Logger)
ObjectFactory objectFactory = Mock(ObjectFactory)
def setup() {
// Is there any better way to say that I need a new object on each interaction with mock?
objectFactory.property(String) >>> [prop(String), prop(String), prop(String), prop(String), prop(String), prop(String), prop(String), prop(String), prop(String)]
objectFactory.property(Integer) >> prop(Integer)
objectFactory.property(Boolean) >> prop(Boolean)
}
def "should parse dependency via string notation"() {
given:
String stringNotation = "com.example:foo:1.0.0:stubs"
def dep = new ContractVerifierExtension.Dependency(stringNotation: stringNotation)
ContractVerifierExtension.Dependency dep = new ContractVerifierExtension.Dependency(objectFactory)
dep.stringNotation.set(stringNotation)
when:
StubConfiguration stubConfig = new GradleContractsDownloader(null, null).stubConfiguration(dep)
then:
@@ -33,12 +44,11 @@ class GradleContractsDownloaderSpec extends Specification {
def "should parse dependency via direct setting"() {
given:
def dep = new ContractVerifierExtension.Dependency(
groupId: "com.example",
artifactId: "foo",
version: "1.0.0",
classifier: "stubs"
)
ContractVerifierExtension.Dependency dep = new ContractVerifierExtension.Dependency(objectFactory)
dep.groupId.set("com.example")
dep.artifactId.set("foo")
dep.version.set("1.0.0")
dep.classifier.set("stubs")
when:
StubConfiguration stubConfig = new GradleContractsDownloader(null, null).stubConfiguration(dep)
then:
@@ -51,8 +61,8 @@ class GradleContractsDownloaderSpec extends Specification {
def "should parse dependency via string notation with methods"() {
given:
String stringNotation = "com.example:foo:1.0.0:stubs"
def dep = new ContractVerifierExtension.Dependency()
dep.stringNotation(stringNotation)
ContractVerifierExtension.Dependency dep = new ContractVerifierExtension.Dependency(objectFactory)
dep.stringNotation.set(stringNotation)
when:
StubConfiguration stubConfig = new GradleContractsDownloader(null, null).stubConfiguration(dep)
then:
@@ -64,11 +74,11 @@ class GradleContractsDownloaderSpec extends Specification {
def "should parse dependency via direct setting with methods"() {
given:
def dep = new ContractVerifierExtension.Dependency()
dep.groupId("com.example")
dep.artifactId("foo")
dep.version("1.0.0")
dep.classifier("stubs")
ContractVerifierExtension.Dependency dep = new ContractVerifierExtension.Dependency(objectFactory)
dep.groupId.set("com.example")
dep.artifactId.set("foo")
dep.version.set("1.0.0")
dep.classifier.set("stubs")
when:
StubConfiguration stubConfig = new GradleContractsDownloader(null, null).stubConfiguration(dep)
then:
@@ -80,158 +90,138 @@ class GradleContractsDownloaderSpec extends Specification {
def "should pick dependency from cache for a non snapshot contract dependency with new property"() {
given:
ContractVerifierExtension ext = new ContractVerifierExtension()
ext.with {
contractsMode = StubRunnerProperties.StubsMode.REMOTE
contractDependency {
groupId("com.example")
artifactId("foo")
version("1.0.0")
classifier("stubs")
}
contractRepository {
repositoryUrl("foo")
}
}
ContractVerifierExtension.Dependency contractDependency = new ContractVerifierExtension.Dependency(objectFactory)
contractDependency.groupId.set("com.example")
contractDependency.artifactId.set("foo")
contractDependency.version.set("1.0.0")
contractDependency.classifier.set("stubs")
ContractVerifierExtension.ContractRepository contractRepository = new ContractVerifierExtension.ContractRepository(objectFactory)
contractRepository.repositoryUrl.set("foo")
and:
final AetherStubDownloader downloader = Mock(AetherStubDownloader)
final ContractDownloader contractDownloader = Mock(ContractDownloader)
and:
def gradleDownloader = stubbedContractDownloader(downloader, contractDownloader)
GradleContractsDownloader 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())
GradleContractsDownloader.DownloadedData downloaded = gradleDownloader.downloadAndUnpackContractsIfRequired(contractDependency, contractRepository, null, StubRunnerProperties.StubsMode.REMOTE, true, [:], true)
then:
file == expectedFileFromCache
downloaded.downloadedContracts == 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 {
contractsMode = StubRunnerProperties.StubsMode.REMOTE
contractDependency {
groupId("com.example")
artifactId("foo")
version("1.0.0")
classifier("stubs")
}
contractRepository {
repositoryUrl("foo")
cacheDownloadedContracts(false)
}
disableStubPublication(true)
}
ContractVerifierExtension.Dependency contractDependency = new ContractVerifierExtension.Dependency(objectFactory)
contractDependency.groupId.set("com.example")
contractDependency.artifactId.set("foo")
contractDependency.version.set("1.0.0")
contractDependency.classifier.set("stubs")
ContractVerifierExtension.ContractRepository contractRepository = new ContractVerifierExtension.ContractRepository(objectFactory)
contractRepository.repositoryUrl.set("foo")
contractRepository.cacheDownloadedContracts.set(false)
and:
final AetherStubDownloader downloader = Mock(AetherStubDownloader)
final ContractDownloader contractDownloader = Mock(ContractDownloader)
and:
def gradleDownloader = stubbedContractDownloader(downloader, contractDownloader)
GradleContractsDownloader 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
contractDownloader.unpackAndDownloadContracts() >> expectedFileNotFromCache
when:
File file = gradleDownloader.downloadAndUnpackContractsIfRequired(ext, new ContractVerifierConfigProperties())
GradleContractsDownloader.DownloadedData downloaded = gradleDownloader.downloadAndUnpackContractsIfRequired(contractDependency, contractRepository, null, StubRunnerProperties.StubsMode.REMOTE, true, [:], true)
then:
file == expectedFileNotFromCache
downloaded.downloadedContracts == expectedFileNotFromCache
}
def "should not pick dependency from cache for snapshot contract dependency"() {
given:
ContractVerifierExtension ext = new ContractVerifierExtension()
ext.with {
contractsMode = StubRunnerProperties.StubsMode.REMOTE
contractDependency {
groupId("com.example")
artifactId("foo")
version("1.0.0.BUILD-SNAPSHOT")
classifier("stubs")
}
contractRepository {
repositoryUrl("foo")
}
}
ContractVerifierExtension.Dependency contractDependency = new ContractVerifierExtension.Dependency(objectFactory)
contractDependency.groupId.set("com.example")
contractDependency.artifactId.set("foo")
contractDependency.version.set("1.0.0.BUILD-SNAPSHOT")
contractDependency.classifier.set("stubs")
ContractVerifierExtension.ContractRepository contractRepository = new ContractVerifierExtension.ContractRepository(objectFactory)
contractRepository.repositoryUrl.set("foo")
and:
final AetherStubDownloader downloader = Mock(AetherStubDownloader)
final ContractDownloader contractDownloader = Mock(ContractDownloader)
File expectedFileNotFromCache = new File("foo/bar/baz")
contractDownloader.unpackedDownloadedContracts(_) >> expectedFileNotFromCache
contractDownloader.unpackAndDownloadContracts() >> expectedFileNotFromCache
and:
def gradleDownloader = stubbedContractDownloader(downloader, contractDownloader)
GradleContractsDownloader gradleDownloader = stubbedContractDownloader(downloader, contractDownloader)
when:
File file = gradleDownloader.downloadAndUnpackContractsIfRequired(ext, new ContractVerifierConfigProperties())
GradleContractsDownloader.DownloadedData downloaded = gradleDownloader.downloadAndUnpackContractsIfRequired(contractDependency, contractRepository, null, StubRunnerProperties.StubsMode.REMOTE, true, [:], true)
then:
file == expectedFileNotFromCache
downloaded.downloadedContracts == expectedFileNotFromCache
}
private GradleContractsDownloader stubbedContractDownloader(downloader, contractDownloader) {
new GradleContractsDownloader(project, logger) {
@Override
protected AetherStubDownloader stubDownloader(ContractVerifierExtension extension) {
protected AetherStubDownloader stubDownloader(ContractVerifierExtension.ContractRepository contractRepository,
StubRunnerProperties.StubsMode contractsMode, boolean deleteStubsAfterTest,
Map<String, String> contractsProperties, boolean failOnNoContracts) {
return downloader
}
@Override
protected ContractDownloader contractDownloader(ContractVerifierExtension extension, StubConfiguration configuration) {
protected ContractDownloader contractDownloader(StubConfiguration configuration,
ContractVerifierExtension.ContractRepository contractRepository,
String contractsPath, StubRunnerProperties.StubsMode contractsMode,
boolean deleteStubsAfterTest, Map<String, String> contractsProperties,
boolean failOnNoContracts) {
return contractDownloader
}
}
}
def "should pick contract directory location from extension"() {
def "should not start downloading"() {
given:
ContractVerifierExtension ext = new ContractVerifierExtension()
ext.with {
contractsDslDir = new File("/foo/bar/baz")
}
ContractVerifierExtension.Dependency contractDependency = new ContractVerifierExtension.Dependency(objectFactory)
ContractVerifierExtension.ContractRepository contractRepository = new ContractVerifierExtension.ContractRepository(objectFactory)
and:
final AetherStubDownloader downloader = Mock(AetherStubDownloader)
final ContractDownloader contractDownloader = Mock(ContractDownloader)
and:
def gradleDownloader = stubbedContractDownloader(downloader, contractDownloader)
GradleContractsDownloader 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())
GradleContractsDownloader.DownloadedData downloaded = gradleDownloader.downloadAndUnpackContractsIfRequired(contractDependency, contractRepository, null, StubRunnerProperties.StubsMode.CLASSPATH, true, [:], true)
then:
file == new File("/foo/bar/baz")
downloaded == null
}
def "should pass contract dependency properties as a parameter to the builder"() {
given:
ContractVerifierExtension ext = new ContractVerifierExtension()
ext.with {
contractDependency {
groupId("com.example")
artifactId("foo")
version("1.0.0.BUILD-SNAPSHOT")
classifier("stubs")
}
contractRepository {
repositoryUrl("foo")
username("foo1")
password("foo2")
proxyHost("foo3")
proxyPort(12)
}
}
ContractVerifierExtension.Dependency contractDependency = new ContractVerifierExtension.Dependency(objectFactory)
contractDependency.groupId.set("com.example")
contractDependency.artifactId.set("foo")
contractDependency.version.set("1.0.0.BUILD-SNAPSHOT")
contractDependency.classifier.set("stubs")
ContractVerifierExtension.ContractRepository contractRepository = new ContractVerifierExtension.ContractRepository(objectFactory)
contractRepository.repositoryUrl.set("foo")
contractRepository.username.set("foo1")
contractRepository.password.set("foo2")
contractRepository.proxyHost.set("foo3")
contractRepository.proxyPort.set(12)
and:
final AetherStubDownloader downloader = Mock(AetherStubDownloader)
final ContractDownloader contractDownloader = Mock(ContractDownloader)
File expectedFileNotFromCache = new File("foo/bar/baz")
contractDownloader.unpackedDownloadedContracts(_) >> expectedFileNotFromCache
contractDownloader.unpackAndDownloadContracts() >> expectedFileNotFromCache
and:
def gradleDownloader = assertingContractDownloader(downloader, contractDownloader)
GradleContractsDownloader gradleDownloader = assertingContractDownloader(downloader, contractDownloader)
when:
gradleDownloader.downloadAndUnpackContractsIfRequired(ext, new ContractVerifierConfigProperties())
gradleDownloader.downloadAndUnpackContractsIfRequired(contractDependency, contractRepository, null, StubRunnerProperties.StubsMode.CLASSPATH, true, [:], true)
then:
noExceptionThrown()
}
@@ -239,7 +229,9 @@ class GradleContractsDownloaderSpec extends Specification {
private GradleContractsDownloader assertingContractDownloader(downloader, contractDownloader) {
new GradleContractsDownloader(project, logger) {
@Override
protected AetherStubDownloader stubDownloader(ContractVerifierExtension extension) {
protected AetherStubDownloader stubDownloader(ContractVerifierExtension.ContractRepository contractRepository,
StubRunnerProperties.StubsMode contractsMode, boolean deleteStubsAfterTest,
Map<String, String> contractsProperties, boolean failOnNoContracts) {
assert extension.contractRepository.username == "foo1"
assert extension.contractRepository.password == "foo2"
assert extension.contractRepository.proxyHost == "foo3"
@@ -248,10 +240,18 @@ class GradleContractsDownloaderSpec extends Specification {
}
@Override
protected ContractDownloader contractDownloader(ContractVerifierExtension extension, StubConfiguration configuration) {
protected ContractDownloader contractDownloader(StubConfiguration configuration,
ContractVerifierExtension.ContractRepository contractRepository,
String contractsPath, StubRunnerProperties.StubsMode contractsMode,
boolean deleteStubsAfterTest, Map<String, String> contractsProperties,
boolean failOnNoContracts) {
return contractDownloader
}
}
}
// Have to use this internal property impl here. Is there some better way?
static <T> Property<T> prop(Class<T> aClass) {
return new DefaultPropertyState(aClass)
}
}

View File

@@ -16,9 +16,12 @@
package org.springframework.cloud.contract.verifier.plugin
import org.gradle.testkit.runner.BuildResult
import org.junit.Ignore
import spock.lang.Stepwise
import static org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE
@Stepwise
@Ignore
class SampleProjectSpec extends ContractVerifierIntegrationSpec {
@@ -32,22 +35,33 @@ class SampleProjectSpec extends ContractVerifierIntegrationSpec {
def "should pass basic flow for Spock"() {
given:
assert fileExists('build.gradle')
expect:
when:
String[] args = ["check", "publishToMavenLocal", "--debug"] as String[]
if (WORK_OFFLINE) {
args << "--offline"
}
runTasksSuccessfully(args)
then:
jarContainsContractVerifierContracts('fraudDetectionService/build/libs')
when: "running generation without change inputs"
BuildResult secondExecutionResult = runTasksSuccessfully()
then: "tasks should be up-to-date"
validateTasksOutcome(secondExecutionResult, UP_TO_DATE, 'generateClientStubs', 'generateContractTests', 'copyContracts')
}
def "should pass basic flow for JUnit"() {
given:
switchToJunitTestFramework()
assert fileExists('build.gradle')
expect:
when:
runTasksSuccessfully(checkAndPublishToMavenLocal())
then:
jarContainsContractVerifierContracts('fraudDetectionService/build/libs')
when: "running generation without change inputs"
BuildResult secondExecutionResult = runTasksSuccessfully()
then: "tasks should be up-to-date"
validateTasksOutcome(secondExecutionResult, UP_TO_DATE, 'generateClientStubs', 'generateContractTests', 'copyContracts')
}
}

View File

@@ -57,7 +57,7 @@ contracts {
testFramework = 'Spock'
}
generateContractTests.dependsOn generateClientStubs
project.tasks.named("generateContractTests").get().dependsOn project.tasks.named("generateClientStubs")
test {
testLogging {

View File

@@ -128,5 +128,5 @@ configure(project(':loanApplicationService')) {
into "src/test/resources/"
}
generateContractTests.dependsOn('copyCollaboratorStubs')
project.tasks.named("generateContractTests").get().dependsOn('copyCollaboratorStubs')
}

View File

@@ -43,6 +43,7 @@ subprojects {
sourceCompatibility = 1.8
targetCompatibility = 1.8
version = "1.0.0"
repositories {
mavenCentral()
@@ -154,5 +155,5 @@ configure(project(':loanApplicationService')) {
into "src/test/resources/mappings"
}
generateContractTests.dependsOn('copyCollaboratorStubs')
project.tasks.named("generateContractTests").get().dependsOn('copyCollaboratorStubs')
}

View File

@@ -215,7 +215,10 @@ public class ConvertMojo extends AbstractMojo {
config.setContractsDslDir(contractsDslDir);
config.setStubsOutputDir(stubsOutputDir(rootPath));
logSetup(config, contractsDslDir);
RecursiveFilesConverter converter = new RecursiveFilesConverter(config);
RecursiveFilesConverter converter = new RecursiveFilesConverter(
config.getStubsOutputDir(), config.getContractsDslDir(),
config.getExcludedFiles(), config.getIncludedContracts(),
config.isExcludeBuildFolders());
converter.processFiles();
}

View File

@@ -106,15 +106,24 @@ class MavenContractsDownloader {
this.log.info(
"Another mojo has downloaded the contracts - will reuse them from ["
+ downloadedContractsDir + "]");
contractDownloader().updatePropertiesWithInclusion(downloadedContractsDir,
config);
final ContractDownloader.InclusionProperties inclusionProperties = contractDownloader()
.createNewInclusionProperties(downloadedContractsDir);
config.setIncludedContracts(inclusionProperties.getIncludedContracts());
config.setIncludedRootFolderAntPattern(
inclusionProperties.getIncludedRootFolderAntPattern());
return downloadedContractsDir;
}
else if (shouldDownloadContracts()) {
this.log.info(
"Download dependency is provided - will retrieve contracts from a remote location");
File downloadedContracts = contractDownloader()
.unpackedDownloadedContracts(config);
final ContractDownloader contractDownloader = contractDownloader();
final File downloadedContracts = contractDownloader
.unpackAndDownloadContracts();
final ContractDownloader.InclusionProperties inclusionProperties = contractDownloader
.createNewInclusionProperties(downloadedContracts);
config.setIncludedContracts(inclusionProperties.getIncludedContracts());
config.setIncludedRootFolderAntPattern(
inclusionProperties.getIncludedRootFolderAntPattern());
this.project.getProperties().setProperty(CONTRACTS_DIRECTORY_PROP,
downloadedContracts.getAbsolutePath());
return downloadedContracts;

View File

@@ -26,7 +26,6 @@ import groovy.transform.PackageScope
import groovy.util.logging.Commons
import org.springframework.cloud.contract.verifier.builder.SingleTestGenerator
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import static org.springframework.cloud.contract.verifier.util.NamesUtil.beforeLast
import static org.springframework.cloud.contract.verifier.util.NamesUtil.capitalize
@@ -39,12 +38,12 @@ class FileSaver {
private final File targetDirectory
private final SingleTestGenerator generator
private final ContractVerifierConfigProperties properties
private final String fileExtension
FileSaver(File targetDirectory, SingleTestGenerator generator, ContractVerifierConfigProperties properties) {
FileSaver(File targetDirectory, String fileExtension, SingleTestGenerator generator) {
this.targetDirectory = targetDirectory
this.generator = generator
this.properties = properties
this.fileExtension = fileExtension
}
void saveClassFile(Path classPath, byte[] classBytes) {
@@ -55,8 +54,7 @@ class FileSaver {
protected Path pathToClass(Path testBaseDir, String fileName) {
return Paths.get(testBaseDir.toString(),
capitalize(fileName) + generator.fileExtension(this.properties)).
toAbsolutePath()
capitalize(fileName) + fileExtension).toAbsolutePath()
}
protected Path generateTestBaseDir(String basePackageClass, String includedDirectoryRelativePath) {

View File

@@ -60,8 +60,8 @@ class TestGenerator {
TestGenerator(ContractVerifierConfigProperties configProperties) {
this(configProperties, singleTestGenerator(),
new FileSaver(configProperties.generatedTestSourcesDir,
singleTestGenerator(), configProperties))
new FileSaver(configProperties.generatedTestSourcesDir, configProperties.testFramework.classExtension,
singleTestGenerator()))
}
private static SingleTestGenerator singleTestGenerator() {

View File

@@ -22,8 +22,6 @@ import java.util.Optional;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties;
import org.springframework.cloud.contract.verifier.util.NamesUtil;
import org.springframework.util.StringUtils;
@@ -33,47 +31,44 @@ class BaseClassProvider {
private static final String SEPARATOR = "_REPLACEME_";
String retrieveBaseClass(ContractVerifierConfigProperties properties,
String retrieveBaseClass(Map<String, String> baseClassMappings, String packageWithBaseClasses, String baseClassForTests,
String includedDirectoryRelativePath) {
String contractPathAsPackage = includedDirectoryRelativePath
.replace(File.separator, ".");
String contractPackage = includedDirectoryRelativePath.replace(File.separator,
SEPARATOR);
// package mapping takes super precedence
if (properties.getBaseClassMappings() != null
&& !properties.getBaseClassMappings().isEmpty()) {
Optional<Map.Entry<String, String>> mapping = properties
.getBaseClassMappings().entrySet().stream().filter(entry -> {
if (baseClassMappings != null && !baseClassMappings.isEmpty()) {
Optional<Map.Entry<String, String>> mapping = baseClassMappings.entrySet().stream().filter(entry -> {
String pattern = entry.getKey();
return contractPathAsPackage.matches(pattern);
}).findFirst();
if (log.isDebugEnabled()) {
log.debug("Matching pattern for contract package ["
+ contractPathAsPackage + "] with setup "
+ properties.getBaseClassMappings() + " is [" + mapping + "]");
+ baseClassMappings + " is [" + mapping
+ "]");
}
if (mapping.isPresent()) {
return mapping.get().getValue();
}
}
if (StringUtils.isEmpty(properties.getPackageWithBaseClasses())) {
return properties.getBaseClassForTests();
if (StringUtils.isEmpty(packageWithBaseClasses)) {
return baseClassForTests;
}
String generatedClassName = generateDefaultBaseClassName(contractPackage,
properties);
String generatedClassName = generateDefaultBaseClassName(contractPackage, packageWithBaseClasses);
return generatedClassName + "Base";
}
private String generateDefaultBaseClassName(String classPackage,
ContractVerifierConfigProperties properties) {
private String generateDefaultBaseClassName(String classPackage, String packageWithBaseClasses) {
String[] splitPackage = NamesUtil.convertIllegalPackageChars(classPackage)
.split(SEPARATOR);
if (splitPackage.length > 1) {
String last = NamesUtil.capitalize(splitPackage[splitPackage.length - 1]);
String butLast = NamesUtil.capitalize(splitPackage[splitPackage.length - 2]);
return properties.getPackageWithBaseClasses() + "." + butLast + last;
return packageWithBaseClasses + "." + butLast + last;
}
return properties.getPackageWithBaseClasses() + "."
return packageWithBaseClasses + "."
+ NamesUtil.capitalize(splitPackage[0]);
}

View File

@@ -27,8 +27,8 @@ interface DefaultBaseClassProvider {
default String fqnBaseClass() {
ContractVerifierConfigProperties properties = generatedClassMetaData().configProperties;
String includedDirectoryRelativePath = generatedClassMetaData().includedDirectoryRelativePath;
return baseClassProvider().retrieveBaseClass(properties,
includedDirectoryRelativePath);
return baseClassProvider().retrieveBaseClass(properties.getBaseClassMappings(), properties.getPackageWithBaseClasses(),
properties.getBaseClassForTests(), includedDirectoryRelativePath);
}
}

View File

@@ -92,7 +92,7 @@ class GenericJsonBodyThen implements Then {
private void addJsonBodyVerification(SingleContractMetadata contractMetadata,
Object responseBody, BodyMatchers bodyMatchers) {
JsonBodyVerificationBuilder jsonBodyVerificationBuilder = new JsonBodyVerificationBuilder(
this.generatedClassMetaData.configProperties, this.templateProcessor,
this.generatedClassMetaData.configProperties.getAssertJsonSize(), this.templateProcessor,
this.contractTemplate, contractMetadata.getContract(),
Optional.of(this.blockBuilder.getLineEnding()),
bodyParser::postProcessJsonPath);

View File

@@ -73,8 +73,8 @@ class GroovyClassMetaData implements ClassMetaData, DefaultClassMetadata {
public ClassMetaData parentClass() {
ContractVerifierConfigProperties properties = generatedClassMetaData().configProperties;
String includedDirectoryRelativePath = generatedClassMetaData().includedDirectoryRelativePath;
String baseClass = baseClassProvider().retrieveBaseClass(properties,
includedDirectoryRelativePath);
String baseClass = baseClassProvider().retrieveBaseClass(properties.getBaseClassMappings(),
properties.getPackageWithBaseClasses(), properties.getBaseClassForTests(), includedDirectoryRelativePath);
baseClass = StringUtils.hasText(baseClass) ? baseClass : "Specification";
int lastIndexOf = baseClass.lastIndexOf(".");
if (lastIndexOf > 0) {

View File

@@ -121,6 +121,7 @@ public class JavaTestGenerator implements SingleTestGenerator {
// @formatter:on
}
@Deprecated
@Override
public String fileExtension(ContractVerifierConfigProperties properties) {
return properties.getTestFramework().getClassExtension();

View File

@@ -34,7 +34,6 @@ import org.springframework.cloud.contract.spec.internal.BodyMatchers;
import org.springframework.cloud.contract.spec.internal.ExecutionProperty;
import org.springframework.cloud.contract.spec.internal.MatchingType;
import org.springframework.cloud.contract.spec.internal.RegexProperty;
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties;
import org.springframework.cloud.contract.verifier.template.TemplateProcessor;
import org.springframework.cloud.contract.verifier.util.JsonPaths;
import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter;
@@ -53,7 +52,7 @@ class JsonBodyVerificationBuilder implements BodyMethodGeneration, ClassVerifier
private static final String FROM_REQUEST_PATH = "path";
private final ContractVerifierConfigProperties configProperties;
private final boolean assertJsonSize;
private final TemplateProcessor templateProcessor;
@@ -69,11 +68,11 @@ class JsonBodyVerificationBuilder implements BodyMethodGeneration, ClassVerifier
// Passing way more arguments here than I would like to, but since we are planning a
// major
// refactoring of this module for Hoxton release, leaving it this way for now
JsonBodyVerificationBuilder(ContractVerifierConfigProperties configProperties,
JsonBodyVerificationBuilder(boolean assertJsonSize,
TemplateProcessor templateProcessor, ContractTemplate contractTemplate,
Contract contract, Optional<String> lineSuffix,
Function<String, String> postProcessJsonPathCall) {
this.configProperties = configProperties;
this.assertJsonSize = assertJsonSize;
this.templateProcessor = templateProcessor;
this.contractTemplate = contractTemplate;
this.contract = contract;
@@ -107,7 +106,7 @@ class JsonBodyVerificationBuilder implements BodyMethodGeneration, ClassVerifier
? TestSideRequestTemplateModel.from(contract.getRequest()) : null;
convertedResponseBody = MapConverter.transformValues(convertedResponseBody,
returnReferencedEntries(templateModel), parsingClosure);
JsonPaths jsonPaths = new JsonToJsonPathsConverter(configProperties)
JsonPaths jsonPaths = new JsonToJsonPathsConverter(assertJsonSize)
.transformToJsonPathWithTestsSideValues(convertedResponseBody,
parsingClosure);
DocumentContext finalParsedRequestBody = parsedRequestBody;

View File

@@ -69,6 +69,7 @@ public interface SingleTestGenerator {
* or {@code .php}
* @param properties - properties passed to the plugin
*/
@Deprecated
String fileExtension(ContractVerifierConfigProperties properties);
class GeneratedClassData {

View File

@@ -57,14 +57,20 @@ class JsonToJsonPathsConverter {
private static final String ANY_ARRAY_NOTATION_IN_JSONPATH = "[*]"
private static final String DESCENDANT_OPERATOR = ".."
private final ContractVerifierConfigProperties configProperties
private final boolean assertJsonSize
// Use constructor with dedicated input param instead
@Deprecated
JsonToJsonPathsConverter(ContractVerifierConfigProperties configProperties) {
this.configProperties = configProperties
assertJsonSize = configProperties.assertJsonSize
}
JsonToJsonPathsConverter(boolean assertJsonSize) {
this.assertJsonSize = assertJsonSize
}
JsonToJsonPathsConverter() {
this(new ContractVerifierConfigProperties())
this(false)
if (log.isTraceEnabled()) {
log.trace("Creating JsonToJsonPaths converter with default properties")
}
@@ -401,7 +407,7 @@ class JsonToJsonPathsConverter {
// Size verification: https://github.com/Codearte/accurest/issues/279
private void addSizeVerificationForListWithPrimitives(MethodBufferingJsonVerifiable key, Closure closure, List value) {
String systemPropValue = System.getProperty(SIZE_ASSERTION_SYSTEM_PROP)
Boolean configPropValue = configProperties.assertJsonSize
Boolean configPropValue = assertJsonSize
if ((systemPropValue != null && Boolean.parseBoolean(systemPropValue))
||
configPropValue && listContainsOnlyPrimitives(value)) {

View File

@@ -1,10 +1,9 @@
package org.springframework.cloud.contract.verifier.builder
import spock.lang.Issue
import spock.lang.Specification
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
/**
* @author Marcin Grzejszczak
*/
@@ -12,72 +11,82 @@ class ClassBuilderSpec extends Specification {
def "should return explicit base class if provided and no default package for base classes is provided"() {
given:
ContractVerifierConfigProperties props = new ContractVerifierConfigProperties(baseClassForTests: 'a.b.Class')
Map<String, String> baseClassMappings = null
String packageWithBaseClasses = null
String baseClassForTests = 'a.b.Class'
expect:
'a.b.Class' == new BaseClassProvider().retrieveBaseClass(props, 'com/example/foo')
'a.b.Class' == new BaseClassProvider().retrieveBaseClass(baseClassMappings, packageWithBaseClasses, baseClassForTests, 'com/example/foo')
}
def "should return a class from the generated path by taking two last folders when package with base classes is provided"() {
given:
ContractVerifierConfigProperties props = new ContractVerifierConfigProperties(packageWithBaseClasses: 'com.example.base')
Map<String, String> baseClassMappings = null
String packageWithBaseClasses = 'com.example.base'
String baseClassForTests = null
String contractRelativeFolder = ['com', 'example', 'some', 'superpackage'].join(File.separator)
expect:
new BaseClassProvider().retrieveBaseClass(props, contractRelativeFolder) == 'com.example.base.SomeSuperpackageBase'
new BaseClassProvider().retrieveBaseClass(baseClassMappings, packageWithBaseClasses, baseClassForTests, contractRelativeFolder) == 'com.example.base.SomeSuperpackageBase'
}
def "should return a class from the generated path by taking two last folders when package with base classes is provided and contains invalid chars"() {
given:
ContractVerifierConfigProperties props = new ContractVerifierConfigProperties(packageWithBaseClasses: 'com.example.base')
Map<String, String> baseClassMappings = null
String packageWithBaseClasses = 'com.example.base'
String baseClassForTests = null
String contractRelativeFolder = ['com', 'example', 'beer-api-producer-external', 'beer-api-consumer'].join(File.separator)
expect:
new BaseClassProvider().retrieveBaseClass(props, contractRelativeFolder) == 'com.example.base.Beer_api_producer_externalBeer_api_consumerBase'
new BaseClassProvider().retrieveBaseClass(baseClassMappings, packageWithBaseClasses, baseClassForTests, contractRelativeFolder) == 'com.example.base.Beer_api_producer_externalBeer_api_consumerBase'
}
def "should return a class from the generated path by taking a single folder when package with base classes is provided and there are not enough package elements"() {
given:
ContractVerifierConfigProperties props = new ContractVerifierConfigProperties(packageWithBaseClasses: 'com.example.base')
Map<String, String> baseClassMappings = null
String packageWithBaseClasses = 'com.example.base'
String baseClassForTests = null
String contractRelativeFolder = 'superpackage'
expect:
new BaseClassProvider().retrieveBaseClass(props, contractRelativeFolder) == 'com.example.base.SuperpackageBase'
new BaseClassProvider().retrieveBaseClass(baseClassMappings, packageWithBaseClasses, baseClassForTests, contractRelativeFolder) == 'com.example.base.SuperpackageBase'
}
def "should return a class from mappings regardless of other entries if mapping exists"() {
given:
ContractVerifierConfigProperties props = new ContractVerifierConfigProperties(
packageWithBaseClasses: 'com.example.base',
baseClassMappings: ['.*': 'com.example.base.SuperClass'])
Map<String, String> baseClassMappings = ['.*': 'com.example.base.SuperClass']
String packageWithBaseClasses = 'com.example.base'
String baseClassForTests = null
String contractRelativeFolder = 'superpackage'
expect:
new BaseClassProvider().retrieveBaseClass(props, contractRelativeFolder) == 'com.example.base.SuperClass'
new BaseClassProvider().retrieveBaseClass(baseClassMappings, packageWithBaseClasses, baseClassForTests, contractRelativeFolder) == 'com.example.base.SuperClass'
}
@Issue("701")
def "should match base class when mapping regex has multiple folders"() {
given:
ContractVerifierConfigProperties props = new ContractVerifierConfigProperties(
baseClassMappings: ['.*bar.baz.some.*': 'com.example.base.SuperClass'])
Map<String, String> baseClassMappings = ['.*bar.baz.some.*': 'com.example.base.SuperClass']
String packageWithBaseClasses = null
String baseClassForTests = null
String contractRelativeFolder = 'foo/bar/baz/some/package'.split("/").join(File.separator)
expect:
new BaseClassProvider().retrieveBaseClass(props, contractRelativeFolder) == 'com.example.base.SuperClass'
new BaseClassProvider().retrieveBaseClass(baseClassMappings, packageWithBaseClasses, baseClassForTests, contractRelativeFolder) == 'com.example.base.SuperClass'
}
def "should return the first matching base class when provided mapping doesn't match"() {
given:
ContractVerifierConfigProperties props = new ContractVerifierConfigProperties(
baseClassForTests: 'a.b.Class',
packageWithBaseClasses: 'com.example.base',
baseClassMappings: ['patternNotMatchingAnything': 'com.example.base.SuperClass'])
Map<String, String> baseClassMappings = ['patternNotMatchingAnything': 'com.example.base.SuperClass']
String packageWithBaseClasses = 'com.example.base'
String baseClassForTests = 'a.b.Class'
String contractRelativeFolder = 'superpackage'
expect:
new BaseClassProvider().retrieveBaseClass(props, contractRelativeFolder) == 'com.example.base.SuperpackageBase'
new BaseClassProvider().retrieveBaseClass(baseClassMappings, packageWithBaseClasses, baseClassForTests, contractRelativeFolder) == 'com.example.base.SuperpackageBase'
}
def "should return a class from the generated path by when external contracts are picked"() {
given:
ContractVerifierConfigProperties props = new ContractVerifierConfigProperties(packageWithBaseClasses: "foo.Bar")
Map<String, String> baseClassMappings = null
String packageWithBaseClasses = 'foo.Bar'
String baseClassForTests = null
String contractRelativeFolder = ["org", "springframework", "cloud", "contract", "verifier", "tests", "META_INF", "com.example", "hello_world", "0.1.0_dev.1.uncommitted+d1174dd"].join(File.separator)
expect:
new BaseClassProvider().retrieveBaseClass(props, contractRelativeFolder) == 'foo.Bar.Hello_world0_1_0_dev_1_uncommitted_d1174ddBase'
new BaseClassProvider().retrieveBaseClass(baseClassMappings, packageWithBaseClasses, baseClassForTests, contractRelativeFolder) == 'foo.Bar.Hello_world0_1_0_dev_1_uncommitted_d1174ddBase'
}
}

View File

@@ -22,6 +22,7 @@ import java.util.function.Supplier;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.verifier.util.ContractVerifierUtil;
// tag::class[]
class contract_docs_examples implements Supplier<Collection<Contract>> {
org.springframework.cloud.contract.spec.Contract httpDsl =

View File

@@ -27,6 +27,7 @@ import org.springframework.cloud.contract.spec.internal.DslProperty;
import org.springframework.cloud.contract.spec.internal.Request;
import org.springframework.cloud.contract.verifier.util.ContractVerifierUtil;
// tag::class[]
class contract_multipart implements Supplier<Collection<Contract>> {
private static Map<String, DslProperty> namedProps(Request r) {

View File

@@ -23,6 +23,7 @@ import java.util.function.Supplier;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.verifier.util.ContractVerifierUtil;
// tag::class[]
class contract_rest implements Supplier<Collection<Contract>> {
@Override

View File

@@ -22,6 +22,7 @@ import java.util.function.Supplier;
import org.springframework.cloud.contract.spec.Contract;
// tag::class[]
class contract_rest_from_file implements Supplier<Collection<Contract>> {
@Override

View File

@@ -23,6 +23,7 @@ import java.util.function.Supplier;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.verifier.util.ContractVerifierUtil;
// tag::class[]
class contract_rest_with_tags implements Supplier<Collection<Contract>> {
static Object description = Collections.singletonList(