From ad9eefd0f522b93111bd8743e844a6043a8f98d6 Mon Sep 17 00:00:00 2001 From: anatoliy-balakirev Date: Wed, 14 Aug 2019 10:48:15 +0200 Subject: [PATCH] Add incremental build support to gradle plugin (#1165) In order to make it working - I had to define clear input and output params for each gradle task. As part of this task I've also cleaned up some services to define clear inputs instead of generic `ContractVerifierConfigProperties`. Fixes gh-1133 --- .../stubrunner/ContractDownloader.java | 87 ++++- .../stubrunner/StubRunnerFactory.java | 11 +- .../stubrunner/ContractDownloaderSpec.groovy | 29 +- .../converter/RecursiveFilesConverter.groovy | 37 +- .../RecursiveFilesConverterSpec.groovy | 62 ++-- .../plugin/ContractVerifierExtension.groovy | 344 +++++++++--------- .../verifier/plugin/ContractsCopyTask.groovy | 183 ++++++---- .../plugin/ExtensionHolderSpec.groovy | 30 -- .../plugin/ExtensionToProperties.groovy | 40 -- .../GenerateClientStubsFromDslTask.groovy | 95 +++-- .../plugin/GenerateServerTestsTask.groovy | 178 +++++---- .../plugin/GradleContractsDownloader.groovy | 162 +++++---- .../verifier/plugin/InitContractsTask.groovy | 90 +++++ .../plugin/OutputFolderBuilder.groovy | 29 -- .../plugin/PublishStubsToScmTask.groovy | 78 ++-- ...ngCloudContractVerifierGradlePlugin.groovy | 291 ++++++++------- .../plugin/StubRunnerOptionsFactory.groovy | 35 ++ .../ContractVerifierExtensionSpec.groovy | 153 -------- .../plugin/ContractVerifierSpec.groovy | 77 ++-- .../GradleContractsDownloaderSpec.groovy | 200 +++++----- .../verifier/plugin/SampleProjectSpec.groovy | 18 +- .../functionalTest/bootSimple/build.gradle | 2 +- .../functionalTest/sampleProject/build.gradle | 2 +- .../scenarioProject/build.gradle | 3 +- .../contract/maven/verifier/ConvertMojo.java | 5 +- .../verifier/MavenContractsDownloader.java | 17 +- .../cloud/contract/verifier/FileSaver.groovy | 10 +- .../contract/verifier/TestGenerator.groovy | 4 +- .../verifier/builder/BaseClassProvider.java | 27 +- .../builder/DefaultBaseClassProvider.java | 4 +- .../verifier/builder/GenericJsonBodyThen.java | 2 +- .../verifier/builder/GroovyClassMetaData.java | 4 +- .../verifier/builder/JavaTestGenerator.java | 1 + .../builder/JsonBodyVerificationBuilder.java | 9 +- .../verifier/builder/SingleTestGenerator.java | 1 + .../util/JsonToJsonPathsConverter.groovy | 14 +- .../verifier/builder/ClassBuilderSpec.groovy | 57 +-- 37 files changed, 1251 insertions(+), 1140 deletions(-) delete mode 100644 spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ExtensionHolderSpec.groovy delete mode 100644 spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ExtensionToProperties.groovy create mode 100644 spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/InitContractsTask.groovy delete mode 100644 spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/OutputFolderBuilder.groovy create mode 100644 spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/StubRunnerOptionsFactory.groovy delete mode 100644 spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtensionSpec.groovy 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' } }