diff --git a/docs/src/main/asciidoc/_project-features-stubrunner.adoc b/docs/src/main/asciidoc/_project-features-stubrunner.adoc index 88b956f8fc..ae3e5a5a13 100644 --- a/docs/src/main/asciidoc/_project-features-stubrunner.adoc +++ b/docs/src/main/asciidoc/_project-features-stubrunner.adoc @@ -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 <> 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] diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/ContractDownloader.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/ContractDownloader.java index b5dba5d5f5..a5149df716 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/ContractDownloader.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/ContractDownloader.java @@ -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 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 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; + } + + } + } diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerFactory.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerFactory.java index 69a17ebc7a..891474c962 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerFactory.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/StubRunnerFactory.java @@ -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(); } diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/ContractDownloaderSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/ContractDownloaderSpec.groovy index 681f031143..84297155e0 100644 --- a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/ContractDownloaderSpec.groovy +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/ContractDownloaderSpec.groovy @@ -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) { diff --git a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/converter/RecursiveFilesConverter.groovy b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/converter/RecursiveFilesConverter.groovy index 65146b7d4b..8088c4b1f9 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/converter/RecursiveFilesConverter.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/converter/RecursiveFilesConverter.groovy @@ -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 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 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 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) diff --git a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/converter/RecursiveFilesConverterSpec.groovy b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/converter/RecursiveFilesConverterSpec.groovy index 55022fe561..3c671322ca 100755 --- a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/converter/RecursiveFilesConverterSpec.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/converter/RecursiveFilesConverterSpec.groovy @@ -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 createdFiles = [] as List - properties.stubsOutputDir. + stubsOutputDir. eachFileRecurse(FileType.FILES) { createdFiles << it } Set 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 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 createdFiles = [] as List - properties.stubsOutputDir. + stubsOutputDir. eachFileRecurse(FileType.FILES) { createdFiles << it } Set 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 getRelativePathsForFilesInDirectory(Collection createdFiles, File targetRootDirectory) { diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy index 0bd6f3cf95..cf0c7087ef 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy @@ -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 /** * Which mechanism should be used to invoke REST calls during tests */ - TestMode testMode = TestMode.MOCKMVC + Property testMode /** * Base package for generated tests */ - String basePackageForTests + Property basePackageForTests /** * Class which all generated tests should extend */ - String baseClassForTests + Property baseClassForTests /** * Suffix for generated test classes, like Spec or Test */ - String nameSuffixForTests + Property nameSuffixForTests /** * Rule class that should be added to generated tests */ - String ruleClassForTests + Property ruleClassForTests /** * Patterns that should not be taken into account for processing */ - List excludedFiles = [] + ListProperty excludedFiles /** * Patterns that should be taken into account for processing */ - List includedFiles = [] + ListProperty includedFiles /** * Patterns for which generated tests should be @Ignored */ - List ignoredFiles = [] + ListProperty ignoredFiles /** * Imports that should be added to generated tests */ - String[] imports = [] + ListProperty imports /** * Static imports that should be added to generated tests */ - String[] staticImports = [] + ListProperty 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 stubsSuffix /** * Incubating feature. You can check the size of JSON arrays. If not turned on * explicitly will be disabled. */ - Boolean assertJsonSize = false + Property assertJsonSize /** * When enabled, this flag will tell stub runner to throw an exception when no stubs / * contracts were found. */ - boolean failOnNoContracts = true + Property 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 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 contractsPath /** * Picks the mode in which stubs will be found and registered */ - StubRunnerProperties.StubsMode contractsMode = StubRunnerProperties.StubsMode.CLASSPATH + Property 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 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 baseClassMappings = [:] + MapProperty 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 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 contractsSnapshotCheckSkip /** * If set to {@code false} will NOT delete stubs from a temporary * folder after running tests */ - boolean deleteStubsAfterTest = true + Property deleteStubsAfterTest /** * If {@code true} then will convert contracts to a YAML representation */ - boolean convertToYaml = false + Property convertToYaml /** * Map of properties that can be passed to custom {@link org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder} */ - Map contractsProperties = [:] + MapProperty contractsProperties void contractDependency(@DelegatesTo(Dependency) Closure closure) { closure.delegate = contractDependency @@ -243,175 +243,167 @@ class ContractVerifierExtension { closure.call() } - void contractsProperties(Map props) { - contractsProperties = props - } - /** * Is set to true will not provide the default publication task */ - boolean disableStubPublication = false + Property 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(this.excludedFiles), - includedFiles: new ArrayList(this.includedFiles), - ignoredFiles: new ArrayList(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(this.baseClassMappings), - excludeBuildFolders: this.excludeBuildFolders, - deleteStubsAfterTest: this.deleteStubsAfterTest, - convertToYaml: this.convertToYaml, - contractsProperties: new HashMap(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 groupId + @Input + @Optional + Property artifactId + @Input + @Optional + Property version + @Input + @Optional + Property classifier + @Input + @Optional + Property 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 delegate + private final MapProperty delegate - BaseClassMapping(Map delegate) { + private BaseClassMapping(MapProperty 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 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 repositoryUrl + @Input + @Optional + Property username + @Input + @Optional + Property password + @Input + @Optional + Property proxyPort + @Input + @Optional + Property proxyHost /** - * Repository URL + * If set to true then will cache the folder where non snapshot contract artifacts got downloaded. */ - String repositoryUrl + @Input + Property 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() + + '}' } } -} +} \ No newline at end of file diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractsCopyTask.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractsCopyTask.groovy index 5278bbc56d..791309ac4a 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractsCopyTask.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractsCopyTask.groovy @@ -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 convertToYaml + @Input + Provider excludeBuildFolders + @Input + Provider failOnNoContracts + @Input + Provider includedRootFolderAntPattern + @InputDirectory + Provider 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, 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() { + @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 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 - } -} +} \ No newline at end of file diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ExtensionHolderSpec.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ExtensionHolderSpec.groovy deleted file mode 100644 index 25981e56f0..0000000000 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ExtensionHolderSpec.groovy +++ /dev/null @@ -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) -} diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ExtensionToProperties.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ExtensionToProperties.groovy deleted file mode 100644 index c6dd3dc560..0000000000 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ExtensionToProperties.groovy +++ /dev/null @@ -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() - ) - } -} diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateClientStubsFromDslTask.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateClientStubsFromDslTask.groovy index 23f72dbc99..f461d0e5ff 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateClientStubsFromDslTask.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateClientStubsFromDslTask.groovy @@ -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 contractsDslDir + @Input + ListProperty excludedFiles + @Input + Provider excludeBuildFolders + + @OutputDirectory + Provider 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 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 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 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 } } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateServerTestsTask.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateServerTestsTask.groovy index ff0f13633b..e47056ecf2 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateServerTestsTask.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateServerTestsTask.groovy @@ -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 contractsDslDir + @Input + Provider includedContracts + @Input + @Optional + Provider nameSuffixForTests + @Input + @Optional + Provider basePackageForTests + @Input + @Optional + Provider baseClassForTests + @Input + @Optional + Provider packageWithBaseClasses + @Input + ListProperty excludedFiles + @Input + ListProperty ignoredFiles + @Input + ListProperty includedFiles + @Input + ListProperty imports + @Input + ListProperty staticImports + @Input + Provider testMode + @Input + Provider testFramework + @Input + MapProperty baseClassMappings + @Input + Provider assertJsonSize + @Input + Provider 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 excludedFiles = config.excludedFiles.get() + List ignoredFiles = config.ignoredFiles.get() + List 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, + TaskProvider 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, + ) } } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GradleContractsDownloader.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GradleContractsDownloader.groovy index 2f2c6081e7..40c7aab394 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GradleContractsDownloader.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GradleContractsDownloader.groovy @@ -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 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 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 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 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 + } } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/InitContractsTask.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/InitContractsTask.groovy new file mode 100644 index 0000000000..6305c5d88e --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/InitContractsTask.groovy @@ -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 contractsMode + @Input + Property deleteStubsAfterTest + @Input + Property failOnNoContracts + @Input + MapProperty contractsProperties + @Input + @Optional + Property contractsPath + + @Internal + Property includedContracts + @Internal + Property 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 + ) + } +} diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/OutputFolderBuilder.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/OutputFolderBuilder.groovy deleted file mode 100644 index 78fb73066e..0000000000 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/OutputFolderBuilder.groovy +++ /dev/null @@ -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}") - } -} diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/PublishStubsToScmTask.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/PublishStubsToScmTask.groovy index 28d8308d41..df5c019c8c 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/PublishStubsToScmTask.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/PublishStubsToScmTask.groovy @@ -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 contractsMode + @Input + Provider deleteStubsAfterTest + @Input + Provider failOnNoContracts + @Input + MapProperty 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 - } } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/SpringCloudContractVerifierGradlePlugin.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/SpringCloudContractVerifierGradlePlugin.groovy index 61c5e40d29..20924ffb74 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/SpringCloudContractVerifierGradlePlugin.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/SpringCloudContractVerifierGradlePlugin.groovy @@ -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 *
    @@ -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 { - 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 { 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 initContracts = createAndConfigureContractsInitTask(extension) + TaskProvider copyContracts = createAndConfigureCopyContractsTask(initContracts, extension) + TaskProvider 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 { 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 copyContracts, TaskProvider initContracts) { + TaskProvider 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 generateClientStubs) { + TaskProvider 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 createAndConfigureGenerateClientStubs(ContractVerifierExtension extension, + TaskProvider copyContracts) { + TaskProvider 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 createAndConfigureStubsJarTasks(ContractVerifierExtension extension, + TaskProvider copyContracts, + TaskProvider generateClientStubs) { + TaskProvider 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 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 stubsTask) { + publishingExtension.publications { + stubs(MavenPublication) { + artifactId "${project.name}" + artifact stubsTask.get() // TODO: How to make it lazily initialised? + } + } + } + + private TaskProvider 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 { } @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 { } } + // TODO: Can we define inputs / outputs and make it incremental? + @CompileDynamic + private TaskProvider createStubsJarTask(ContractVerifierExtension extension, + TaskProvider generateClientStubs) { + TaskProvider 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 createAndConfigureCopyContractsTask(TaskProvider initContractsTask, + ContractVerifierExtension extension) { + TaskProvider 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 createAndConfigureContractsInitTask(ContractVerifierExtension extension) { + TaskProvider 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 { } 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}" + } } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/StubRunnerOptionsFactory.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/StubRunnerOptionsFactory.groovy new file mode 100644 index 0000000000..9ad7e22e60 --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/StubRunnerOptionsFactory.groovy @@ -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 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() + } +} diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtensionSpec.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtensionSpec.groovy deleted file mode 100644 index 6038a6bb58..0000000000 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtensionSpec.groovy +++ /dev/null @@ -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"] - } -} diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierSpec.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierSpec.groovy index 44af04e6a1..32b3897e30 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierSpec.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierSpec.groovy @@ -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') diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/GradleContractsDownloaderSpec.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/GradleContractsDownloaderSpec.groovy index e24fff864b..983b9f1d76 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/GradleContractsDownloaderSpec.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/GradleContractsDownloaderSpec.groovy @@ -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 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 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 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 contractsProperties, + boolean failOnNoContracts) { return contractDownloader } } } + // Have to use this internal property impl here. Is there some better way? + static Property prop(Class aClass) { + return new DefaultPropertyState(aClass) + } } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/SampleProjectSpec.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/SampleProjectSpec.groovy index 552fe5173f..dae3cb595f 100755 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/SampleProjectSpec.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/SampleProjectSpec.groovy @@ -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') } } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/bootSimple/build.gradle b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/bootSimple/build.gradle index 19b3a0412e..e9a09d84bc 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/bootSimple/build.gradle +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/bootSimple/build.gradle @@ -57,7 +57,7 @@ contracts { testFramework = 'Spock' } -generateContractTests.dependsOn generateClientStubs +project.tasks.named("generateContractTests").get().dependsOn project.tasks.named("generateClientStubs") test { testLogging { diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleProject/build.gradle b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleProject/build.gradle index d3ec3ce217..87a648e058 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleProject/build.gradle +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleProject/build.gradle @@ -128,5 +128,5 @@ configure(project(':loanApplicationService')) { into "src/test/resources/" } - generateContractTests.dependsOn('copyCollaboratorStubs') + project.tasks.named("generateContractTests").get().dependsOn('copyCollaboratorStubs') } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/build.gradle b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/build.gradle index 0b1c78981f..0b4d5ae3f6 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/build.gradle +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/build.gradle @@ -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') } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/ConvertMojo.java b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/ConvertMojo.java index b4fd3d5564..c2d22ae8ed 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/ConvertMojo.java +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/ConvertMojo.java @@ -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(); } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/MavenContractsDownloader.java b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/MavenContractsDownloader.java index cebddd9409..3db4be220e 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/MavenContractsDownloader.java +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/MavenContractsDownloader.java @@ -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; diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/FileSaver.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/FileSaver.groovy index 4a4e966d1d..94b6627eec 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/FileSaver.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/FileSaver.groovy @@ -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) { diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/TestGenerator.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/TestGenerator.groovy index ee2933db8f..71e7d93d73 100755 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/TestGenerator.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/TestGenerator.groovy @@ -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() { diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/BaseClassProvider.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/BaseClassProvider.java index 93efcb86bc..e9875f2492 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/BaseClassProvider.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/BaseClassProvider.java @@ -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 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> mapping = properties - .getBaseClassMappings().entrySet().stream().filter(entry -> { + if (baseClassMappings != null && !baseClassMappings.isEmpty()) { + Optional> 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]); } diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/DefaultBaseClassProvider.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/DefaultBaseClassProvider.java index 8c1a0504ec..2f2bba10c0 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/DefaultBaseClassProvider.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/DefaultBaseClassProvider.java @@ -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); } } diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/GenericJsonBodyThen.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/GenericJsonBodyThen.java index 60956a5938..5edf35727c 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/GenericJsonBodyThen.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/GenericJsonBodyThen.java @@ -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); diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/GroovyClassMetaData.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/GroovyClassMetaData.java index 228c1a1dcf..89b50f3d56 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/GroovyClassMetaData.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/GroovyClassMetaData.java @@ -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) { diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JavaTestGenerator.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JavaTestGenerator.java index 37770ad339..2f4355ef23 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JavaTestGenerator.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JavaTestGenerator.java @@ -121,6 +121,7 @@ public class JavaTestGenerator implements SingleTestGenerator { // @formatter:on } + @Deprecated @Override public String fileExtension(ContractVerifierConfigProperties properties) { return properties.getTestFramework().getClassExtension(); diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JsonBodyVerificationBuilder.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JsonBodyVerificationBuilder.java index dfaa83f883..98531e1d56 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JsonBodyVerificationBuilder.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JsonBodyVerificationBuilder.java @@ -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 lineSuffix, Function 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; diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGenerator.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGenerator.java index eb8280924c..598f679ea1 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGenerator.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGenerator.java @@ -69,6 +69,7 @@ public interface SingleTestGenerator { * or {@code .php} * @param properties - properties passed to the plugin */ + @Deprecated String fileExtension(ContractVerifierConfigProperties properties); class GeneratedClassData { diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy index f6423b1e76..daaefb2d94 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy @@ -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)) { diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ClassBuilderSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ClassBuilderSpec.groovy index 36c4eadd5d..42e39576f2 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ClassBuilderSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ClassBuilderSpec.groovy @@ -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 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 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 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 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 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 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 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 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' } } diff --git a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_docs_examples.java b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_docs_examples.java index 7213f08f32..c0bc3d79ab 100644 --- a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_docs_examples.java +++ b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_docs_examples.java @@ -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> { org.springframework.cloud.contract.spec.Contract httpDsl = diff --git a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_multipart.java b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_multipart.java index c813fc1ca5..669dc79b63 100644 --- a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_multipart.java +++ b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_multipart.java @@ -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> { private static Map namedProps(Request r) { diff --git a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest.java b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest.java index e650990866..7d1b717666 100644 --- a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest.java +++ b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest.java @@ -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> { @Override diff --git a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_from_file.java b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_from_file.java index 9dfc8a0e30..5a5aeb8115 100644 --- a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_from_file.java +++ b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_from_file.java @@ -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> { @Override diff --git a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_with_tags.java b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_with_tags.java index d5b632bc3a..07b5ca9fba 100644 --- a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_with_tags.java +++ b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_with_tags.java @@ -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> { static Object description = Collections.singletonList(