Convert Gradle plugin from Groovy to Java (#1480)

* Migrate ContractsCopyTask from Groovy to Java

* Convert code to Java

* Refactor ContractsCopyTask

* Migrate GenerateClientStubsFromDslTask from Groovy to Java.

* Refactor GenerateClientStubsFromDslTask

* Clean up configuration of stubsJar

* Migrate GenerateServerTestsTask from Groovy to Java

* Refactor GenerateServerTestsTask

* Migrate PublishStubsToScmTask from Groovy to Java

* Refactor PublishStubsToScmTask

* Remove dead code

* Add additional test to ensure merged contract repository settings works the same as it did before. Add .get() calls to trigger task configuration otherwise, we can miss errors with task configuration.

* Fixup all visibility modifiers and drop any deprecated functionality

* Migrate plugin from Groovy to Java

* Refactor plugin from Groovy to Java

* Fixup project.version where it might not be correctly available yet

* Fix tests that were broken

* Fix functional testing that was failing

* Update functional tests to ensure that up-to-date checks are running as expected

* Added comment around deleteStubsAfterTest in each of the tasks where it is used, to remind editors what the property is for.

* Enable support for build cache

* Reconcile documentation with code

* Remove TODO that has been answered

* Add documentation on how to customize the verifierStubsJar

* Fix failing test

* Allow contractsDirectory to be null

* Fix failing tests

* Move all main source code to java source set
This commit is contained in:
Shannon Pamperl
2020-10-02 10:39:53 -05:00
committed by GitHub
parent b12fc7ae32
commit e91b91efba
25 changed files with 1724 additions and 1460 deletions

View File

@@ -228,22 +228,13 @@ pseudocode):
contracts {
testFramework ='JUNIT'
testMode = 'MockMvc'
generatedTestSourcesDir = project.file("${project.buildDir}/generated-test-sources/contracts")
generatedTestJavaSourcesDir = project.file("${project.buildDir}/generated-test-sources/contractTest/java")
generatedTestGroovySourcesDir = project.file("${project.buildDir}/generated-test-sources/contractTest/groovy")
generatedTestResourcesDir = project.file("${project.buildDir}/generated-test-resources/contracts")
contractsDslDir = project.file("${project.rootDir}/src/test/resources/contracts")
contractsDslDir = project.file("${project.projectDir}/src/test/resources/contracts")
basePackageForTests = 'org.springframework.cloud.verifier.tests'
stubsOutputDir = project.file("${project.buildDir}/stubs")
sourceSet = null
// the following properties are used when you want to provide where the JAR with contract lays
contractDependency {
stringNotation = ''
}
contractsPath = ''
contractsWorkOffline = false
contractRepository {
cacheDownloadedContracts(true)
}
}
tasks.create(type: Jar, name: 'verifierStubsJar', dependsOn: 'generateClientStubs') {
@@ -278,6 +269,52 @@ contracts {
----
====
To download contracts from a remote source, you can use the following snippets as needed:
====
[source,groovy,indent=0]
----
contracts {
// If your contracts exist in a JAR archive published to a Maven repository
contractDependency {
stringNotation = ''
// OR
groupId = ''
artifactId = ''
version = ''
classifier = ''
}
// If your contracts exist in a Git SCM repository
contractRepository {
repositoryUrl = ''
// username = ''
// password = ''
}
// controls the nested location to find the contracts in either the JAR or Git SCM source
contractsPath = ''
}
----
====
Since we are using Gradle's Jar packaging task, there are several options and capabilities that you may wish to utilize to further extend what is created by the `verifierStubsJar`. In order to do this, you would use the native mechanisms provided directly by Gradle for customizing an existing task like so:
NOTE: for the sake of the example, we desire to add a `git.properties` file to the `verifierStubsJar`.
====
[source,groovy,inden=0]
----
verifierStubsJar {
from("${buildDir}/resources/main/") {
include("git.properties")
}
}
----
====
It should also be noted that as of 3.0.0, the default publication has been disabled. As a result this means, that you are able to create any named jar and publish it as you would normally have done via Gradle configuration options. This means that you can build a jar file customized just the way you would like and publish that for absolute full control over the jar's layout and contents.
[[gradle-configuration-options]]
== Configuration Options
@@ -299,25 +336,24 @@ use Spock classes, the class is `spock.lang.Specification`.
setting takes precedence over `baseClassForTests`.
* `baseClassMappings`: Explicitly maps a contract package to a FQN of a base class. This
setting takes precedence over `packageWithBaseClasses` and `baseClassForTests`.
* `ruleClassForTests`: Specifies a rule that should be added to the generated test
classes.
* `ignoredFiles`: Uses an `Antmatcher` to allow defining stub files for which processing
should be skipped. By default, it is an empty array.
* `contractsDslDir`: Specifies the directory that contains contracts written by using the
GroovyDSL. By default, its value is `$rootDir/src/test/resources/contracts`.
GroovyDSL. By default, its value is `$projectDir/src/test/resources/contracts`.
* `generatedTestSourcesDir`: Specifies the test source directory where tests generated
from the Groovy DSL should be placed. By default, its value is
`$buildDir/generated-test-sources/contracts`.
from the Groovy DSL should be placed. (Deprecrated)
* `generatedTestJavaSourcesDir`: Specifies the test source directory where Java/JUnit tests generated from the Groovy DSL should be placed. By default, it's value is `$buildDir/generated-tes-sources/contractTest/java`.
* `generatedTestGroovySourcesDir`: Specifies the test source directory where Groovy/Spock tests generated from the Groovy DSL should be placed. By default, it's value is `$buildDir/generated-test-sources/contractTest/groovy`.
* `generatedTestResourcesDir`: Specifies the test resource directory where resources used by the tests generated
from the Groovy DSL should be placed. By default, its value is
`$buildDir/generated-test-resources/contracts`.
`$buildDir/generated-test-resources/contractTest`.
* `stubsOutputDir`: Specifies the directory where the generated WireMock stubs from
the Groovy DSL should be placed.
* `testFramework`: Specifies the target test framework to be used. Currently, Spock, JUnit 4 (`TestFramework.JUNIT`) and
JUnit 5 are supported, with JUnit 4 being the default framework.
* `contractsProperties`: A map that contains properties to be passed to Spring Cloud Contract
components. Those properties might be used by (for example) built-in or custom Stub Downloaders.
* `sourceSet`: Source set where the contracts are stored. If not provided will assume `test` (for example, `project.sourceSets.test.java` for JUnit or `project.sourceSets.test.groovy` for Spock).
* `sourceSet`: Source set where the contracts are stored. If not provided will assume `contractTest` (for example, `project.sourceSets.contractTest.java` for JUnit or `project.sourceSets.contractTest.groovy` for Spock).
You can use the following properties when you want to specify the location of the JAR
that contains the contracts:

View File

@@ -81,7 +81,7 @@ dependencies {
}
}
test {
contractTest {
useJUnitPlatform()
systemProperty 'spring.profiles.active', 'gradle'
testLogging {
@@ -97,6 +97,25 @@ test {
}
}
publishing {
publications {
maven(MavenPublication) {
artifact bootJar
artifact verifierStubsJar
// https://github.com/spring-gradle-plugins/dependency-management-plugin/issues/273
versionMapping {
usage("java-api") {
fromResolutionOf("runtimeClasspath")
}
usage("java-runtime") {
fromResolutionResult()
}
}
}
}
}
clean.doFirst {
delete "~/.m2/repository/com/example/http-server-dsl-gradle"
}

View File

@@ -4,7 +4,6 @@ plugins {
id "io.spring.dependency-management"
id "maven-publish"
id "maven"
id "org.springframework.cloud.contract"
}
group = 'com.example'
@@ -36,10 +35,6 @@ dependencies {
testImplementation 'org.springframework.cloud:spring-cloud-contract-wiremock'
}
contracts {
failOnNoContracts = false
}
test {
useJUnitPlatform()
systemProperty 'spring.profiles.active', 'gradle'
@@ -83,6 +78,25 @@ task copyClasses(type: Copy) {
into "${project.buildDir}/stubs/"
}
publishing {
publications {
maven(MavenPublication) {
artifact bootJar
artifact stubsJar
// https://github.com/spring-gradle-plugins/dependency-management-plugin/issues/273
versionMapping {
usage("java-api") {
fromResolutionOf("runtimeClasspath")
}
usage("java-runtime") {
fromResolutionResult()
}
}
}
}
}
clean.doFirst {
delete 'target/snippets/stubs'
delete "~/.m2/repository/com/example/http-server-restdocs-gradle"

View File

@@ -63,7 +63,6 @@ configurations {
dependencies {
compile gradleApi()
compile localGroovy()
compile "org.eclipse.aether:aether-api:${aetherVersion}"
compile("org.springframework.cloud:spring-cloud-contract-converters:${project.version}") {
@@ -79,6 +78,7 @@ dependencies {
exclude(group: 'org.codehaus.groovy')
}
testCompile 'info.solidsoft.spock:spock-global-unroll:0.5.0'
testCompile localGroovy()
testCompile gradleTestKit()
}

View File

@@ -1,300 +0,0 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.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 java.time.Instant
import groovy.transform.CompileStatic
import groovy.transform.Immutable
import groovy.transform.ImmutableOptions
import groovy.transform.PackageScope
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
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.logging.Logger
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
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.gradle.api.tasks.WorkResult
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.spring.StubRunnerProperties
import org.springframework.cloud.contract.verifier.converter.ToYamlConverter
import org.springframework.util.StringUtils
// 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 DefaultTask {
static final String TASK_NAME = 'copyContracts'
static final String CONTRACTS = "contracts"
static final String BACKUP = "original"
@Nested
Config config
static class Config {
@Input
Provider<Boolean> convertToYaml
@Input
Provider<Boolean> excludeBuildFolders
@Input
Provider<Boolean> failOnNoContracts
String contractsDirectoryPath
Provider<Directory> contractsDirectory
// All fields inside `@Nested` one are properly marked as an `@Input` to work with incremental build:
@Nested
@Optional
ContractVerifierExtension.Dependency contractDependency
@Nested
@Optional
ContractVerifierExtension.ContractRepository contractRepository
@Input
@Optional
Property<StubRunnerProperties.StubsMode> contractsMode
@Input
Property<Boolean> deleteStubsAfterTest
@Input
MapProperty<String, String> contractsProperties
@Input
@Optional
Property<String> contractsPath
@Input
@Optional
Instant getForceDownloadOfTheLatestContracts() {
// If we have `dynamic` version (`+` or `SNAPSHOT`) - we should mark this task as out of date for every run:
if (shouldDownloadContracts() && getStubConfiguration().isVersionChanging()) {
return Instant.now() // This will trigger re-download of contracts
}
else {
return null // This will not trigger re-download of contracts
}
}
@Optional
@InputDirectory
Provider<Directory> getContractsDirectory() {
contractsDirectoryPath = contractsDirectory.get().asFile.absolutePath
if (shouldDownloadContracts() || contractFolderMissing()) {
return null
}
else {
return contractsDirectory
}
}
private boolean contractFolderMissing() {
return contractsDirectory.isPresent() && !contractsDirectory.get().asFile.exists()
}
@Internal
boolean shouldDownloadContracts() {
return StringUtils.hasText(contractDependency.getArtifactId().getOrNull()) ||
StringUtils.hasText(contractDependency.getStringNotation().getOrNull()) ||
StringUtils.hasText(contractRepository.repositoryUrl.getOrNull())
}
@Internal
StubConfiguration getStubConfiguration() {
return GradleContractsDownloaderHelper.stubConfiguration(contractDependency)
}
@OutputDirectory
DirectoryProperty copiedContractsFolder
@OutputDirectory
DirectoryProperty stubsOutputDir
@Optional
@OutputDirectory
DirectoryProperty backupContractsFolder
}
@TaskAction
void sync() {
final DownloadedData downloadedData = downloadContractsIfNeeded()
final File contractsDirectory
String antPattern = ""
if (downloadedData) {
contractsDirectory = downloadedData.downloadedContracts
antPattern = "${downloadedData.inclusionProperties.includedRootFolderAntPattern}*.*"
logger.info("Contracts got downloaded to [" + contractsDirectory + "]")
}
else if (config.contractsDirectory != null && config.contractsDirectory.isPresent()) {
contractsDirectory = config.contractsDirectory.get().asFile
antPattern = "**/"
}
else {
contractsDirectory = null
}
logger.info("For project [{}] will use contracts provided in the folder [{}]", project.name, contractsDirectory)
final String contractsRepository = config.contractRepository.repositoryUrl.isPresent() ? config.contractRepository.repositoryUrl.get() : ""
throwExceptionWhenFailOnNoContracts(contractsDirectory, contractsRepository)
if (contractsDirectory == null) {
logger.info("Contracts directory not set and contracts weren't downloaded. There's nothing to copy")
return
}
final String slashSeparatedGroupId = project.group.toString().replace(".", File.separator)
final String slashSeparatedAntPattern = antPattern.replace(slashSeparatedGroupId, project.group.toString())
File output = config.copiedContractsFolder.get().getAsFile()
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, String root, Project project) {
return new Config(
convertToYaml: extension.convertToYaml,
excludeBuildFolders: extension.excludeBuildFolders,
failOnNoContracts: extension.failOnNoContracts,
contractsDirectory: extension.contractsDslDir,
stubsOutputDir: extension.stubsOutputDir,
copiedContractsFolder: createTaskOutput(root, extension.stubsOutputDir, ContractsCopyTask.CONTRACTS, project),
backupContractsFolder: createTaskOutput(root, extension.stubsOutputDir, ContractsCopyTask.BACKUP, project),
contractDependency: extension.contractDependency,
contractRepository: extension.contractRepository,
contractsMode: extension.contractsMode,
deleteStubsAfterTest: extension.deleteStubsAfterTest,
contractsProperties: extension.contractsProperties,
contractsPath: extension.contractsPath
)
}
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) {
return project.sync(new Action<CopySpec>() {
@Override
void execute(final CopySpec spec) {
spec.with {
from(file)
// by default group id is slash separated...
include(antPattern)
// ...we also want to allow dot separation
include(slashSeparatedAntPattern)
if (excludeBuildFolders) {
exclude "**/target/**", "**/build/**", "**/.mvn/**", "**/.gradle/**"
}
into(outputContractsFolder)
}
}
})
}
private DownloadedData downloadContractsIfNeeded() {
if (config.shouldDownloadContracts()) {
logger.info("Project has group id [{}], artifact id [{}]", project.group, project.name)
logger.info("For project [${project.name}] Download dependency is provided - will download contract jars")
logger.info("Contract dependency [{}]", config.contractDependency)
StubConfiguration configuration = config.getStubConfiguration()
logger.info("Got the following contract dependency to download [{}]", configuration)
logger.info("The contract dependency is a changing one [{}]", configuration.isVersionChanging())
final StubDownloader downloader = new StubDownloaderBuilderProvider().get(
StubRunnerOptionsFactory.createStubRunnerOptions(config.contractRepository,
config.contractsMode.getOrNull(), config.deleteStubsAfterTest.get(),
config.contractsProperties.get(), config.failOnNoContracts.get()))
final ContractDownloader contractDownloader = new ContractDownloader(downloader, configuration,
config.contractsPath.getOrNull(), project.group as String, project.name, project.version as String)
final File downloadedContracts = contractDownloader.unpackAndDownloadContracts();
final ContractDownloader.InclusionProperties inclusionProperties =
contractDownloader.createNewInclusionProperties(downloadedContracts)
// TODO: inclusionProperties.includedContracts is never used eventually. Review this:
return new DownloadedData(
downloadedContracts: contractsSubDirIfPresent(downloadedContracts, logger),
inclusionProperties: inclusionProperties
)
}
else {
return null
}
}
private static DirectoryProperty createTaskOutput(String root, DirectoryProperty stubsOutputDir, String suffix, Project project) {
Provider<Directory> provider = stubsOutputDir.flatMap {
Directory dir = it
File output = project.file("${dir.asFile}/${root}/${suffix}")
DirectoryProperty property = project.objects.directoryProperty()
property.set(output)
return property
}
DirectoryProperty property = project.objects.directoryProperty();
property.set(provider)
return property
}
private void throwExceptionWhenFailOnNoContracts(File file, String contractsRepository) {
if (StringUtils.hasText(contractsRepository)) {
if (logger.isDebugEnabled()) {
logger.debug("Contracts repository is set, will not throw an exception that the contracts are not found")
}
return
}
if (config.failOnNoContracts.get() && (file == null || !file.exists() || file.listFiles().length == 0)) {
String path = file != null ? file.getAbsolutePath() : config.contractsDirectoryPath
throw new GradleException("Contracts could not be found: [" + path + "]\nPlease make sure that the contracts were defined, or set the [failOnNoContracts] flag to [false]")
}
}
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
}
return contractsDirectory
}
@ImmutableOptions(knownImmutableClasses = [File, ContractDownloader.InclusionProperties])
@Immutable
private static class DownloadedData {
final File downloadedContracts
final ContractDownloader.InclusionProperties inclusionProperties
}
}

View File

@@ -1,98 +0,0 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.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 groovy.transform.CompileStatic
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.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.
*
* @author Marcin Grzejszczak
* @author Anatoliy Balakirev
* @since 2.0.0
*/
@CompileStatic
class GenerateClientStubsFromDslTask extends DefaultTask {
static final String TASK_NAME = 'generateClientStubs'
private static final String DEFAULT_MAPPINGS_FOLDER = 'mappings'
@Nested
Config config
static class Config {
@InputDirectory
Provider<Directory> contractsDslDir
@Input
ListProperty<String> excludedFiles
@Input
Provider<Boolean> excludeBuildFolders
@OutputDirectory
Provider<Directory> stubsOutputDir
}
@TaskAction
void generate() {
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")
logger.info("Contracts dir is [${config.contractsDslDir.get().asFile}] output stubs dir is [${output}]")
List<String> excludedFiles = config.excludedFiles.get()
RecursiveFilesConverter converter = new RecursiveFilesConverter(output,
config.contractsDslDir.get().asFile, excludedFiles, ".*", config.excludeBuildFolders.get())
converter.processFiles()
}
static Config fromExtension(ContractVerifierExtension extension, TaskProvider<ContractsCopyTask> copyContracts,
String root, Project project) {
return new Config(
contractsDslDir: copyContracts.flatMap { it.config.copiedContractsFolder },
excludedFiles: extension.excludedFiles,
excludeBuildFolders: extension.excludeBuildFolders,
stubsOutputDir: createTaskOutput(root, extension.stubsOutputDir, project)
)
}
private static DirectoryProperty createTaskOutput(String root, DirectoryProperty stubsOutputDir, Project project) {
Provider<Directory> provider = stubsOutputDir.flatMap {
Directory dir = it
File output = new File(dir.asFile, "${root}/${DEFAULT_MAPPINGS_FOLDER}")
DirectoryProperty property = project.objects.directoryProperty();
property.set(output)
return property
}
DirectoryProperty property = project.objects.directoryProperty();
property.set(provider)
return property
}
}

View File

@@ -1,251 +0,0 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.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 groovy.transform.CompileStatic
import groovy.transform.builder.Builder
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
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.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 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 DefaultTask {
static final String TASK_NAME = 'generateContractTests'
@Nested
Config config
@CompileStatic
@Builder
static class Config {
final Provider<Directory> contractsDslDir
final Provider<String> nameSuffixForTests
final Provider<String> basePackageForTests
final Provider<String> baseClassForTests
final Provider<String> packageWithBaseClasses
final ListProperty<String> excludedFiles
final ListProperty<String> ignoredFiles
final ListProperty<String> includedFiles
final ListProperty<String> imports
final ListProperty<String> staticImports
final Provider<TestMode> testMode
final Provider<TestFramework> testFramework
final MapProperty<String, String> baseClassMappings
final Provider<Boolean> assertJsonSize
final Provider<Boolean> failOnInProgress
final DirectoryProperty generatedTestSourcesDir
final DirectoryProperty generatedTestResourcesDir
Config(Provider<Directory> contractsDslDir, Provider<String> nameSuffixForTests, Provider<String> basePackageForTests, Provider<String> baseClassForTests, Provider<String> packageWithBaseClasses, ListProperty<String> excludedFiles, ListProperty<String> ignoredFiles, ListProperty<String> includedFiles, ListProperty<String> imports, ListProperty<String> staticImports, Provider<TestMode> testMode, Provider<TestFramework> testFramework, MapProperty<String, String> baseClassMappings, Provider<Boolean> assertJsonSize, Provider<Boolean> failOnInProgress, DirectoryProperty generatedTestSourcesDir, DirectoryProperty generatedTestResourcesDir) {
this.contractsDslDir = contractsDslDir
this.nameSuffixForTests = nameSuffixForTests
this.basePackageForTests = basePackageForTests
this.baseClassForTests = baseClassForTests
this.packageWithBaseClasses = packageWithBaseClasses
this.excludedFiles = excludedFiles
this.ignoredFiles = ignoredFiles
this.includedFiles = includedFiles
this.imports = imports
this.staticImports = staticImports
this.testMode = testMode
this.testFramework = testFramework
this.baseClassMappings = baseClassMappings
this.assertJsonSize = assertJsonSize
this.failOnInProgress = failOnInProgress
this.generatedTestSourcesDir = generatedTestSourcesDir
this.generatedTestResourcesDir = generatedTestResourcesDir
}
@InputDirectory
Provider<Directory> getContractsDslDir() {
return contractsDslDir
}
@Input
@Optional
Provider<String> getNameSuffixForTests() {
return nameSuffixForTests
}
@Input
@Optional
Provider<String> getBasePackageForTests() {
return basePackageForTests
}
@Input
@Optional
Provider<String> getBaseClassForTests() {
return baseClassForTests
}
@Input
@Optional
Provider<String> getPackageWithBaseClasses() {
return packageWithBaseClasses
}
@Input
ListProperty<String> getExcludedFiles() {
return excludedFiles
}
@Input
ListProperty<String> getIgnoredFiles() {
return ignoredFiles
}
@Input
ListProperty<String> getIncludedFiles() {
return includedFiles
}
@Input
ListProperty<String> getImports() {
return imports
}
@Input
ListProperty<String> getStaticImports() {
return staticImports
}
@Input
Provider<TestMode> getTestMode() {
return testMode
}
@Input
Provider<TestFramework> getTestFramework() {
return testFramework
}
@Input
MapProperty<String, String> getBaseClassMappings() {
return baseClassMappings
}
@Input
Provider<Boolean> getAssertJsonSize() {
return assertJsonSize
}
@Input
Provider<Boolean> getFailOnInProgress() {
return failOnInProgress
}
@OutputDirectory
DirectoryProperty getGeneratedTestSourcesDir() {
return generatedTestSourcesDir
}
@OutputDirectory
DirectoryProperty getGeneratedTestResourcesDir() {
return generatedTestResourcesDir
}
}
@TaskAction
void generate() {
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 = ".*"
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 [${includedContracts}]")
try {
List<String> excludedFiles = config.excludedFiles.get()
List<String> ignoredFiles = config.ignoredFiles.get()
List<String> includedFiles = config.includedFiles.get()
String[] imports = config.imports.get().toArray(new String[0])
String[] staticImports = config.staticImports.get().toArray(new String[0])
TestGenerator generator = new TestGenerator(new ContractVerifierConfigProperties(
includedContracts: includedContracts,
contractsDslDir: contractsDslDir,
nameSuffixForTests: config.nameSuffixForTests.getOrNull(),
generatedTestSourcesDir: generatedTestSources,
generatedTestResourcesDir: generatedTestResources,
basePackageForTests: config.basePackageForTests.getOrNull(),
baseClassForTests: config.baseClassForTests.getOrNull(),
packageWithBaseClasses: config.packageWithBaseClasses.getOrNull(),
excludedFiles: excludedFiles,
ignoredFiles: ignoredFiles,
includedFiles: includedFiles,
imports: imports,
staticImports: staticImports,
testMode: config.testMode.get(),
testFramework: config.testFramework.get(),
baseClassMappings: config.baseClassMappings.get(),
assertJsonSize: config.assertJsonSize.get(),
failOnInProgress: config.failOnInProgress.get()
))
int generatedClasses = generator.generate()
project.logger.info("Generated {} test classes", generatedClasses)
}
catch (ContractVerifierException e) {
throw new GradleException("Spring Cloud Contract Verifier Plugin exception: ${e.message}", e)
}
}
static Config fromExtension(ContractVerifierExtension extension, TaskProvider<ContractsCopyTask> copyContractsTask) {
return new Config(
copyContractsTask.flatMap { it.config.copiedContractsFolder },
extension.nameSuffixForTests,
extension.basePackageForTests,
extension.baseClassForTests,
extension.packageWithBaseClasses,
extension.excludedFiles,
extension.ignoredFiles,
extension.includedFiles,
extension.imports,
extension.staticImports,
extension.testMode,
extension.testFramework,
extension.baseClassMappings.getBaseClassMappings(),
extension.assertJsonSize,
extension.failOnInProgress,
extension.generatedTestSourcesDir,
extension.generatedTestResourcesDir)
}
}

View File

@@ -1,33 +0,0 @@
package org.springframework.cloud.contract.verifier.plugin
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.springframework.cloud.contract.stubrunner.StubConfiguration
import org.springframework.util.StringUtils
/**
* @author Anatoliy Balakirev
*/
@PackageScope
@CompileStatic
class GradleContractsDownloaderHelper {
private static final String LATEST_VERSION = '+'
@PackageScope
static StubConfiguration stubConfiguration(ContractVerifierExtension.Dependency contractDependency) {
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,
stubConfiguration.version, stubConfiguration.classifier)
}
return new StubConfiguration(groupId, artifactId, version, classifier)
}
}

View File

@@ -1,134 +0,0 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.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 groovy.transform.CompileStatic
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.model.ObjectFactory
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.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
* to the cloned repo with contracts and stubs. Will also
* commit the changes and push them to origin.
*
* NOTE: starting with 2.3.0.RELEASE the <code>customize{}</code> closure previously used for
* {@link PublishStubsToScmTask} customisation is no longer available. The settings should be applied directly
* within the <code>publishStubsToScm</code> closure as in the example above.
*
* @author Marcin Grzejszczak
* @author Anatoliy Balakirev
* @since 2.0.0
*/
@CompileStatic
class PublishStubsToScmTask extends DefaultTask {
static final String TASK_NAME = 'publishStubsToScm'
@Nested
Config config
@CompileStatic
static class Config {
@Nested
final Property<ContractVerifierExtension.ContractRepository> contractRepository
@Nested
final Property<ContractVerifierExtension.PublishStubsToScm> publishStubsToScm
@Input
final Property<StubRunnerProperties.StubsMode> contractsMode
@Input
final Property<Boolean> deleteStubsAfterTest
@Input
final Property<Boolean> failOnNoContracts
@Input
final MapProperty<String, String> contractsProperties
@OutputDirectory
final DirectoryProperty stubsOutputDir
@Internal
final ObjectFactory objects
Config(ObjectFactory objects, ContractVerifierExtension extension) {
this.objects = objects
this.contractRepository = objects.property(ContractVerifierExtension.ContractRepository)
this.contractRepository.set(extension.contractRepository)
this.publishStubsToScm = objects.property(ContractVerifierExtension.PublishStubsToScm)
this.publishStubsToScm.set(extension.publishStubsToScm)
this.contractsMode = objects.property(StubRunnerProperties.StubsMode)
this.contractsMode.set(extension.contractsMode)
this.deleteStubsAfterTest = objects.property(Boolean)
this.deleteStubsAfterTest.set(extension.failOnNoContracts)
this.failOnNoContracts = objects.property(Boolean)
this.failOnNoContracts.set(extension.deleteStubsAfterTest)
this.contractsProperties = objects.mapProperty(String, String)
this.contractsProperties.set(extension.contractsProperties)
this.stubsOutputDir = objects.directoryProperty()
this.stubsOutputDir.set(extension.stubsOutputDir)
}
}
@TaskAction
void publishStubsToScm() {
ContractVerifierExtension.ContractRepository repository = merged()
if (!shouldRun(repository)) {
return
}
String projectName = project.group.toString() + ":" + project.name.toString() + ":" + this.project.version.toString()
project.logger.info("Pushing Stubs to SCM for project [" + projectName + "]")
StubRunnerOptions stubRunnerOptions = StubRunnerOptionsFactory.createStubRunnerOptions(
repository, config.contractsMode.getOrElse(StubRunnerProperties.StubsMode.REMOTE), config.deleteStubsAfterTest.get(),
config.contractsProperties.get(), config.failOnNoContracts.get())
new ContractProjectUpdater(stubRunnerOptions).updateContractProject(projectName, config.stubsOutputDir.get().asFile.toPath())
}
static Config fromExtension(ContractVerifierExtension extension, ObjectFactory objects) {
return new Config(objects, extension)
}
private boolean shouldRun(ContractVerifierExtension.ContractRepository repository) {
String contractRepoUrl = repository.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
}
private ContractVerifierExtension.ContractRepository merged() {
ContractVerifierExtension.ContractRepository original = config.contractRepository.get()
ContractVerifierExtension.ContractRepository stubs = config.publishStubsToScm.get().getContractRepository()
ContractVerifierExtension.ContractRepository copied = new ContractVerifierExtension.ContractRepository(config.objects)
copied.setRepositoryUrl(stubs.getRepositoryUrl().getOrElse(original.getRepositoryUrl().getOrNull()))
copied.setUsername(stubs.getUsername().getOrElse(original.getUsername().getOrNull()))
copied.setPassword(stubs.getPassword().getOrElse(original.getPassword().getOrNull()))
Integer port = stubs.getProxyPort().getOrElse(original.getProxyPort().getOrNull())
if (port != null ) {
copied.setProxyPort(port)
}
copied.setProxyHost(stubs.getProxyHost().getOrElse(original.getProxyHost().getOrNull()))
return copied
}
}

View File

@@ -1,276 +0,0 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.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 groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import org.gradle.api.Plugin
import org.gradle.api.Project
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.api.tasks.bundling.Jar
import org.springframework.cloud.contract.verifier.config.TestFramework
/**
* Gradle plugin for Spring Cloud Contract Verifier that from the DSL contract can
* <ul>
* <li>generate tests</li>
* <li>generate stubs</li>
* </ul>
*
* @author Jakub Kubrynski, codearte.io
* @author Marcin Grzejszczak
* @author Anatoliy Balakirev
*
* @since 1.0.0
*/
@CompileStatic
class SpringCloudContractVerifierGradlePlugin implements Plugin<Project> {
private static final String VERIFIER_STUBS_JAR_TASK_NAME = 'verifierStubsJar'
private static final String GROUP_NAME = "Verification"
private static final String EXTENSION_NAME = 'contracts'
private Project project
@Override
void apply(Project project) {
this.project = project
project.plugins.apply(GroovyPlugin)
ContractVerifierExtension extension = project.extensions.create(EXTENSION_NAME, ContractVerifierExtension)
TaskProvider<ContractsCopyTask> copyContracts = createAndConfigureCopyContractsTask(extension)
TaskProvider<GenerateClientStubsFromDslTask> generateClientStubs = createAndConfigureGenerateClientStubs(extension, copyContracts)
createAndConfigureStubsJarTasks(extension, copyContracts, generateClientStubs)
createGenerateTestsTask(extension, copyContracts)
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 applyDefaultSourceSets(ContractVerifierExtension extension) {
boolean sourceSetPresent = extension.getSourceSet().isPresent()
String sourceSet = sourceSet(sourceSetPresent, extension)
String sourceSetType = extension.testFramework.get() == TestFramework.SPOCK ? "groovy" : "java"
project.sourceSets."${sourceSet}"."${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
}
}
private String sourceSet(boolean sourceSetPresent, ContractVerifierExtension extension) {
if (sourceSetPresent) {
return extension.getSourceSet().get()
}
return "test"
}
// This must be called within afterEvaluate due to getting data from extension, which must be initialised first:
@CompileDynamic
private addIdeaTestSources(Project project, ContractVerifierExtension extension) {
boolean hasIdea = new File(project.rootDir, ".idea").exists()
if (hasIdea) {
project.apply(plugin: 'idea')
project.idea {
module {
testSourceDirs += extension.generatedTestSourcesDir.get().asFile
testSourceDirs += extension.generatedTestResourcesDir.get().asFile
testSourceDirs += extension.contractsDslDir.get().asFile
}
}
}
}
private void createGenerateTestsTask(ContractVerifierExtension extension, TaskProvider<ContractsCopyTask> copyContracts) {
TaskProvider<GenerateServerTestsTask> task = project.tasks.register(GenerateServerTestsTask.TASK_NAME, GenerateServerTestsTask)
task.configure {
it.description = "Generate server tests from the contracts"
it.group = GROUP_NAME
it.enabled = !project.gradle.startParameter.excludedTaskNames.contains("test")
it.config = GenerateServerTestsTask.fromExtension(extension, copyContracts)
it.dependsOn copyContracts
}
project.tasks.findByName("compileTestJava").dependsOn(task)
project.tasks.findByName("check").dependsOn(task)
}
private void createAndConfigurePublishStubsToScmTask(ContractVerifierExtension extension,
TaskProvider<GenerateClientStubsFromDslTask> generateClientStubs) {
TaskProvider<PublishStubsToScmTask> task = project.tasks.register(PublishStubsToScmTask.TASK_NAME, PublishStubsToScmTask)
task.configure {
it.description = "The generated stubs get committed to the SCM repo and pushed to origin"
it.group = GROUP_NAME
it.config = PublishStubsToScmTask.fromExtension(extension, project.objects)
it.dependsOn generateClientStubs
}
}
private TaskProvider<GenerateClientStubsFromDslTask> createAndConfigureGenerateClientStubs(ContractVerifierExtension extension,
TaskProvider<ContractsCopyTask> copyContracts) {
TaskProvider<GenerateClientStubsFromDslTask> task = project.tasks.register(GenerateClientStubsFromDslTask.TASK_NAME, GenerateClientStubsFromDslTask)
task.configure {
it.description = "Generate client stubs from the contracts"
it.group = GROUP_NAME
it.config = GenerateClientStubsFromDslTask.fromExtension(extension, copyContracts, buildRootPath(), project)
it.dependsOn copyContracts
}
return task
}
private TaskProvider<Task> createAndConfigureStubsJarTasks(ContractVerifierExtension extension,
TaskProvider<ContractsCopyTask> copyContracts,
TaskProvider<GenerateClientStubsFromDslTask> generateClientStubs) {
TaskProvider<Task> task = stubsTask()
if (task) {
// How is this possible? Where can it come from?
project.logger.info("Spring Cloud Contract Verifier Plugin: Stubs jar task was present - won't create one. Remember about adding it to artifacts as an archive!")
}
else {
task = createStubsJarTask(extension, generateClientStubs)
}
task.configure {
it.dependsOn copyContracts
}
createAndConfigureMavenPublishPlugin(task, extension)
return task
}
@Deprecated
private void createAndConfigureMavenPublishPlugin(TaskProvider<Task> stubsTask, ContractVerifierExtension extension) {
if (!classIsOnClasspath("org.gradle.api.publish.maven.plugins.MavenPublishPlugin")) {
project.logger.debug("Maven Publish Plugin is not present - won't add default publication")
return
}
// This must be called within afterEvaluate due to getting data from extension, which must be initialised first:
project.afterEvaluate {
project.logger.debug("Spring Cloud Contract Verifier Plugin: Generating default publication")
if (extension.disableStubPublication.get()) {
project.logger.info("You've switched off the stub publication - won't add default publication")
return
}
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)
}
}
}
}
@CompileDynamic
@Deprecated
private void setPublications(def publishingExtension, TaskProvider<Task> stubsTask) {
project.logger.warn("Spring Cloud Contract Verifier Plugin: Creating stubs publication is deprecated")
publishingExtension.publications {
stubs(MavenPublication) {
artifactId "${project.name}"
artifact stubsTask.get() // TODO: How to make it lazily initialised?
}
}
}
private TaskProvider<Task> stubsTask() {
try {
return project.tasks.named(VERIFIER_STUBS_JAR_TASK_NAME)
}
catch (Exception e) {
return null
}
}
@CompileDynamic
@Deprecated
private boolean hasStubsPublication(def publishingExtension) {
try {
return publishingExtension.publications.getByName('stubs')
}
catch (Exception e) {
return false
}
}
// TODO: Can we define inputs / outputs and make it incremental?
@CompileDynamic
private TaskProvider<Task> createStubsJarTask(ContractVerifierExtension extension,
TaskProvider<GenerateClientStubsFromDslTask> generateClientStubs) {
TaskProvider<Jar> task = project.tasks.register(VERIFIER_STUBS_JAR_TASK_NAME, Jar)
task.configure {
it.description = "Creates the stubs JAR task"
it.group = GROUP_NAME
it.getArchiveBaseName().set(project.name)
it.getArchiveClassifier().set(extension.stubsSuffix)
it.from { extension.stubsOutputDir }
it.dependsOn generateClientStubs
}
project.artifacts {
archives task
}
return task
}
private TaskProvider<ContractsCopyTask> createAndConfigureCopyContractsTask(ContractVerifierExtension extension) {
TaskProvider<ContractsCopyTask> task = project.tasks.register(ContractsCopyTask.TASK_NAME, ContractsCopyTask)
task.configure {
it.description = "Copies contracts to the output folder"
it.group = GROUP_NAME
it.config = ContractsCopyTask.fromExtension(extension, buildRootPath(), project)
}
return task
}
@Deprecated
private boolean classIsOnClasspath(String className) {
try {
Class.forName(className)
return true
}
catch (Exception e) {
project.logger.debug("Maven Publish Plugin is not available")
}
return false
}
private String buildRootPath() {
String groupId = project.group as String
String artifactId = project.name
String version = project.version
return "META-INF/${groupId}/${artifactId}/${version}"
}
}

View File

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

View File

@@ -23,14 +23,9 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import javax.inject.Inject;
import groovy.lang.Closure;
import groovy.lang.DelegatesTo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.gradle.api.Action;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.ProjectLayout;
@@ -38,11 +33,6 @@ 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.Internal;
import org.gradle.api.tasks.Nested;
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;
@@ -55,100 +45,104 @@ import org.springframework.util.Assert;
*/
public class ContractVerifierExtension implements Serializable {
private static final Log log = LogFactory.getLog(ContractVerifierExtension.class);
/**
* For which unit test library tests should be generated
*/
private Property<TestFramework> testFramework;
private final Property<TestFramework> testFramework;
/**
* Which mechanism should be used to invoke REST calls during tests
*/
private Property<TestMode> testMode;
private final Property<TestMode> testMode;
/**
* Base package for generated tests
*/
private Property<String> basePackageForTests;
private final Property<String> basePackageForTests;
/**
* Class which all generated tests should extend
*/
private Property<String> baseClassForTests;
private final Property<String> baseClassForTests;
/**
* Suffix for generated test classes, like Spec or Test
*/
private Property<String> nameSuffixForTests;
/**
* Rule class that should be added to generated tests
*/
private Property<String> ruleClassForTests;
private final Property<String> nameSuffixForTests;
/**
* Patterns that should not be taken into account for processing
*/
private ListProperty<String> excludedFiles;
private final ListProperty<String> excludedFiles;
/**
* Patterns that should be taken into account for processing
*/
private ListProperty<String> includedFiles;
private final ListProperty<String> includedFiles;
/**
* Patterns for which generated tests should be @Ignored
*/
private ListProperty<String> ignoredFiles;
private final ListProperty<String> ignoredFiles;
/**
* Imports that should be added to generated tests
*/
private ListProperty<String> imports;
private final ListProperty<String> imports;
/**
* Static imports that should be added to generated tests
*/
private ListProperty<String> staticImports;
private final ListProperty<String> staticImports;
/**
* Directory containing contracts written using the GroovyDSL
*/
private DirectoryProperty contractsDslDir;
private final DirectoryProperty contractsDslDir;
/**
* Test source directory where tests generated from Groovy DSL should be placed
*/
private DirectoryProperty generatedTestSourcesDir;
@Deprecated
private final DirectoryProperty generatedTestSourcesDir;
/**
* Java test source directory where tests generated from Contract DSL should be placed
*/
private final DirectoryProperty generatedTestJavaSourcesDir;
/**
* Groovy test source directory where tests generated from Contract DSL should be placed
*/
private final DirectoryProperty generatedTestGroovySourcesDir;
/**
* Test resource directory where tests generated from Groovy DSL should be referenced
*/
private DirectoryProperty generatedTestResourcesDir;
private final 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
*/
private DirectoryProperty stubsOutputDir;
private final DirectoryProperty stubsOutputDir;
/**
* Suffix for the generated Stubs Jar task
*/
private Property<String> stubsSuffix;
private final Property<String> stubsSuffix;
/**
* Incubating feature. You can check the size of JSON arrays. If not turned on
* explicitly will be disabled.
*/
private Property<Boolean> assertJsonSize;
private final Property<Boolean> assertJsonSize;
/**
* When enabled, this flag will tell stub runner to throw an exception when no stubs /
* contracts were found.
*/
private Property<Boolean> failOnNoContracts;
private final Property<Boolean> failOnNoContracts;
/**
* If set to true then if any contracts that are in progress are found, will break the
@@ -156,16 +150,16 @@ public class ContractVerifierExtension implements Serializable {
* contracts in progress and take into consideration that you might be causing false
* positive test execution results on the consumer side.
*/
private Property<Boolean> failOnInProgress;
private final Property<Boolean> failOnInProgress;
private ContractRepository contractRepository;
private final ContractRepository contractRepository;
private PublishStubsToScm publishStubsToScm;
private final PublishStubsToScm publishStubsToScm;
/**
* Dependency that contains packaged contracts
*/
private Dependency contractDependency;
private final Dependency contractDependency;
/**
* The path in the JAR with all the contracts where contracts for this particular service lay.
@@ -173,12 +167,12 @@ public class ContractVerifierExtension implements Serializable {
* If {@code groupid} is {@code com.example} and {@code artifactid} is {@code service} then the resolved path will be
* {@code /com/example/artifactid}
*/
private Property<String> contractsPath;
private final Property<String> contractsPath;
/**
* Picks the mode in which stubs will be found and registered
*/
private Property<StubRunnerProperties.StubsMode> contractsMode;
private final Property<StubRunnerProperties.StubsMode> contractsMode;
/**
* A package that contains all the base clases for generated tests. If your contract resides in a location
@@ -187,7 +181,7 @@ public class ContractVerifierExtension implements Serializable {
* 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.
*/
private Property<String> packageWithBaseClasses;
private final Property<String> packageWithBaseClasses;
/**
* A way to override any base class mappings. The keys are regular expressions on the package name
@@ -197,39 +191,30 @@ public class ContractVerifierExtension implements Serializable {
* 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}
*/
private BaseClassMapping baseClassMappings;
private final BaseClassMapping 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.
*/
private Property<Boolean> excludeBuildFolders;
/**
* If set to {@code true} will not assert whether the downloaded stubs / contract
* JAR was downloaded from a remote location or a local one(only applicable to Maven repos, not Git or Pact)
*
* @deprecated - with 2.1.0 this option is redundant
*/
@Deprecated
private Property<Boolean> contractsSnapshotCheckSkip;
private final Property<Boolean> excludeBuildFolders;
/**
* If set to {@code false} will NOT delete stubs from a temporary
* folder after running tests
*/
private Property<Boolean> deleteStubsAfterTest;
private final Property<Boolean> deleteStubsAfterTest;
/**
* If {@code true} then will convert contracts to a YAML representation
*/
private Property<Boolean> convertToYaml;
private final Property<Boolean> convertToYaml;
/**
* Map of properties that can be passed to custom {@link org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder}
*/
private MapProperty<String, String> contractsProperties;
private final MapProperty<String, String> contractsProperties;
/**
* Is set to true will not provide the default publication task
@@ -237,14 +222,12 @@ public class ContractVerifierExtension implements Serializable {
* @deprecated - with 3.0.0, the user should include stubs with their own publication(s)
*/
@Deprecated
private Property<Boolean> disableStubPublication;
private final Property<Boolean> disableStubPublication;
/**
* Source set where the contracts are stored. If not provided will assume {@code test}.
*/
private Property<String> sourceSet;
private final ObjectFactory objects;
private final Property<String> sourceSet;
@Inject
public ContractVerifierExtension(ProjectLayout layout, ObjectFactory objects) {
@@ -253,15 +236,16 @@ public class ContractVerifierExtension implements Serializable {
this.basePackageForTests = objects.property(String.class);
this.baseClassForTests = objects.property(String.class);
this.nameSuffixForTests = objects.property(String.class);
this.ruleClassForTests = objects.property(String.class);
this.excludedFiles = objects.listProperty(String.class).convention(new ArrayList<>());
this.includedFiles = objects.listProperty(String.class).convention(new ArrayList<>());
this.ignoredFiles = objects.listProperty(String.class).convention(new ArrayList<>());
this.imports = objects.listProperty(String.class).convention(new ArrayList<>());
this.staticImports = objects.listProperty(String.class).convention(new ArrayList<>());
this.contractsDslDir = objects.directoryProperty().convention(layout.getProjectDirectory().dir("src/test/resources/contracts"));
this.generatedTestSourcesDir = objects.directoryProperty().convention(layout.getBuildDirectory().dir("generated-test-sources/contracts"));
this.generatedTestResourcesDir = objects.directoryProperty().convention(layout.getBuildDirectory().dir("generated-test-resources/contracts"));
this.generatedTestSourcesDir = objects.directoryProperty();
this.generatedTestJavaSourcesDir = objects.directoryProperty().convention(layout.getBuildDirectory().dir("generated-test-sources/contractTest/java"));
this.generatedTestGroovySourcesDir = objects.directoryProperty().convention(layout.getBuildDirectory().dir("generated-test-sources/contractTest/groovy"));
this.generatedTestResourcesDir = objects.directoryProperty().convention(layout.getBuildDirectory().dir("generated-test-resources/contractTest"));
this.stubsOutputDir = objects.directoryProperty().convention(layout.getBuildDirectory().dir("stubs"));
this.stubsSuffix = objects.property(String.class).convention("stubs");
this.assertJsonSize = objects.property(Boolean.class).convention(false);
@@ -275,24 +259,11 @@ public class ContractVerifierExtension implements Serializable {
this.packageWithBaseClasses = objects.property(String.class);
this.baseClassMappings = objects.newInstance(BaseClassMapping.class);
this.excludeBuildFolders = objects.property(Boolean.class).convention(false);
this.contractsSnapshotCheckSkip = objects.property(Boolean.class).convention(false);
this.deleteStubsAfterTest = objects.property(Boolean.class).convention(true);
this.convertToYaml = objects.property(Boolean.class).convention(false);
this.contractsProperties = objects.mapProperty(String.class, String.class).convention(new HashMap<>());
this.disableStubPublication = objects.property(Boolean.class).convention(true);
this.sourceSet = objects.property(String.class);
this.objects = objects;
}
@Deprecated
public void setTargetFramework(TestFramework targetFramework) {
log.warn("Please use the [testFramework] field. This one is deprecated");
this.testFramework.set(targetFramework);
}
@Deprecated
public TestFramework getTargetFramework() {
return getTestFramework().get();
}
public Property<TestFramework> getTestFramework() {
@@ -304,9 +275,7 @@ public class ContractVerifierExtension implements Serializable {
}
public void setTestFramework(String testFramework) {
if (testFramework != null) {
this.testFramework.set(TestFramework.valueOf(testFramework.toUpperCase()));
}
this.testFramework.set(TestFramework.valueOf(testFramework.toUpperCase()));
}
public Property<TestMode> getTestMode() {
@@ -318,9 +287,7 @@ public class ContractVerifierExtension implements Serializable {
}
public void setTestMode(String testMode) {
if (testMode != null) {
this.testMode.set(TestMode.valueOf(testMode.toUpperCase()));
}
this.testMode.set(TestMode.valueOf(testMode.toUpperCase()));
}
public Property<String> getBasePackageForTests() {
@@ -347,14 +314,6 @@ public class ContractVerifierExtension implements Serializable {
this.nameSuffixForTests.set(nameSuffixForTests);
}
public Property<String> getRuleClassForTests() {
return ruleClassForTests;
}
public void setRuleClassForTests(String ruleClassForTests) {
this.ruleClassForTests.set(ruleClassForTests);
}
public ListProperty<String> getExcludedFiles() {
return excludedFiles;
}
@@ -411,14 +370,32 @@ public class ContractVerifierExtension implements Serializable {
this.contractsDslDir.set(contractsDslDir);
}
@Deprecated
public DirectoryProperty getGeneratedTestSourcesDir() {
return generatedTestSourcesDir;
}
@Deprecated
public void setGeneratedTestSourcesDir(File generatedTestSourcesDir) {
this.generatedTestSourcesDir.set(generatedTestSourcesDir);
}
public DirectoryProperty getGeneratedTestJavaSourcesDir() {
return generatedTestJavaSourcesDir;
}
public void setGeneratedTestJavaSourcesDir(File generatedTestJavaSourcesDir) {
this.generatedTestJavaSourcesDir.set(generatedTestJavaSourcesDir);
}
public DirectoryProperty getGeneratedTestGroovySourcesDir() {
return generatedTestGroovySourcesDir;
}
public void setGeneratedTestGroovySourcesDir(File generatedTestGroovySourcesDir) {
this.generatedTestGroovySourcesDir.set(generatedTestGroovySourcesDir);
}
public DirectoryProperty getGeneratedTestResourcesDir() {
return generatedTestResourcesDir;
}
@@ -467,7 +444,6 @@ public class ContractVerifierExtension implements Serializable {
this.failOnInProgress.set(failOnInProgress);
}
@Nested
public ContractRepository getContractRepository() {
return contractRepository;
}
@@ -476,7 +452,6 @@ public class ContractVerifierExtension implements Serializable {
action.execute(contractRepository);
}
@Nested
public PublishStubsToScm getPublishStubsToScm() {
return publishStubsToScm;
}
@@ -510,9 +485,7 @@ public class ContractVerifierExtension implements Serializable {
}
public void setContractsMode(String contractsMode) {
if (contractsMode != null) {
this.contractsMode.set(StubRunnerProperties.StubsMode.valueOf(contractsMode.toUpperCase()));
}
this.contractsMode.set(StubRunnerProperties.StubsMode.valueOf(contractsMode.toUpperCase()));
}
public Property<String> getPackageWithBaseClasses() {
@@ -543,14 +516,6 @@ public class ContractVerifierExtension implements Serializable {
this.excludeBuildFolders.set(excludeBuildFolders);
}
public Property<Boolean> getContractsSnapshotCheckSkip() {
return contractsSnapshotCheckSkip;
}
public void setContractsSnapshotCheckSkip(boolean contractsSnapshotCheckSkip) {
this.contractsSnapshotCheckSkip.set(contractsSnapshotCheckSkip);
}
public Property<Boolean> getDeleteStubsAfterTest() {
return deleteStubsAfterTest;
}
@@ -575,12 +540,6 @@ public class ContractVerifierExtension implements Serializable {
this.contractsProperties.set(contractsProperties);
}
// use standard setter instead
@Deprecated
public void contractsProperties(Map<String, String> map) {
contractsProperties.set(map);
}
@Deprecated
public Property<Boolean> getDisableStubPublication() {
return disableStubPublication;
@@ -600,11 +559,11 @@ public class ContractVerifierExtension implements Serializable {
}
public static class Dependency implements Serializable {
private Property<String> groupId;
private Property<String> artifactId;
private Property<String> version;
private Property<String> classifier;
private Property<String> stringNotation;
private final Property<String> groupId;
private final Property<String> artifactId;
private final Property<String> version;
private final Property<String> classifier;
private final Property<String> stringNotation;
@Inject
public Dependency(ObjectFactory objects) {
@@ -615,8 +574,6 @@ public class ContractVerifierExtension implements Serializable {
stringNotation = objects.property(String.class);
}
@Input
@Optional
public Property<String> getGroupId() {
return groupId;
}
@@ -625,8 +582,6 @@ public class ContractVerifierExtension implements Serializable {
this.groupId.set(groupId);
}
@Input
@Optional
public Property<String> getArtifactId() {
return artifactId;
}
@@ -635,8 +590,6 @@ public class ContractVerifierExtension implements Serializable {
this.artifactId.set(artifactId);
}
@Input
@Optional
public Property<String> getVersion() {
return version;
}
@@ -645,8 +598,6 @@ public class ContractVerifierExtension implements Serializable {
this.version.set(version);
}
@Input
@Optional
public Property<String> getClassifier() {
return classifier;
}
@@ -655,8 +606,6 @@ public class ContractVerifierExtension implements Serializable {
this.classifier.set(classifier);
}
@Input
@Optional
public Property<String> getStringNotation() {
return stringNotation;
}
@@ -700,17 +649,11 @@ public class ContractVerifierExtension implements Serializable {
// This class is used as an input to the tasks, so all fields are marked as `@Input` to allow incremental build
public static class ContractRepository implements Serializable {
private Property<String> repositoryUrl;
private Property<String> username;
private Property<String> password;
private Property<Integer> proxyPort;
private Property<String> proxyHost;
/**
* If set to true then will cache the folder where non snapshot contract artifacts got downloaded.
*
* Not used any more, as we switched to Gradle's incremental build.
*/
private Property<Boolean> cacheDownloadedContracts;
private final Property<String> repositoryUrl;
private final Property<String> username;
private final Property<String> password;
private final Property<Integer> proxyPort;
private final Property<String> proxyHost;
@Inject
public ContractRepository(ObjectFactory objects) {
@@ -719,11 +662,8 @@ public class ContractVerifierExtension implements Serializable {
this.password = objects.property(String.class);
this.proxyHost = objects.property(String.class);
this.proxyPort = objects.property(Integer.class);
this.cacheDownloadedContracts = objects.property(Boolean.class).convention(true);
}
@Input
@Optional
public Property<String> getRepositoryUrl() {
return repositoryUrl;
}
@@ -732,14 +672,6 @@ public class ContractVerifierExtension implements Serializable {
this.repositoryUrl.set(repositoryUrl);
}
// favor property assignment
@Deprecated
public void repositoryUrl(String repositoryUrl) {
this.repositoryUrl.set(repositoryUrl);
}
@Input
@Optional
public Property<String> getUsername() {
return username;
}
@@ -748,14 +680,6 @@ public class ContractVerifierExtension implements Serializable {
this.username.set(username);
}
// favor property assignment
@Deprecated
public void username(String username) {
this.username.set(username);
}
@Input
@Optional
public Property<String> getPassword() {
return password;
}
@@ -764,14 +688,6 @@ public class ContractVerifierExtension implements Serializable {
this.password.set(password);
}
// favor property assignment
@Deprecated
public void password(String password) {
this.password.set(password);
}
@Input
@Optional
public Property<String> getProxyHost() {
return proxyHost;
}
@@ -780,14 +696,6 @@ public class ContractVerifierExtension implements Serializable {
this.proxyHost.set(proxyHost);
}
// favor property assignment
@Deprecated
public void proxyHost(String proxyHost) {
this.proxyHost.set(proxyHost);
}
@Input
@Optional
public Property<Integer> getProxyPort() {
return proxyPort;
}
@@ -797,17 +705,6 @@ public class ContractVerifierExtension implements Serializable {
this.proxyPort.set(proxyPort);
}
@Internal
@Deprecated
public Property<Boolean> getCacheDownloadedContracts() {
return cacheDownloadedContracts;
}
@Deprecated
public void setCacheDownloadedContracts(boolean cacheDownloadedContracts) {
this.cacheDownloadedContracts.set(cacheDownloadedContracts);
}
@Override
public String toString() {
return "ContractRepository{" +
@@ -816,7 +713,6 @@ public class ContractVerifierExtension implements Serializable {
", password=" + password.getOrNull() +
", proxyPort=" + proxyPort.getOrNull() +
", proxyHost=" + proxyHost.getOrNull() +
", cacheDownloadedContracts=" + cacheDownloadedContracts.get() +
'}';
}
}
@@ -826,9 +722,9 @@ public class ContractVerifierExtension implements Serializable {
/**
* Dependency that contains packaged contracts
*/
private Dependency contractDependency;
private final Dependency contractDependency;
private ContractRepository contractRepository;
private final ContractRepository contractRepository;
@Inject
public PublishStubsToScm(ObjectFactory objects) {
@@ -836,7 +732,6 @@ public class ContractVerifierExtension implements Serializable {
contractRepository = objects.newInstance(ContractRepository.class);
}
@Nested
public Dependency getContractDependency() {
return contractDependency;
}
@@ -845,7 +740,6 @@ public class ContractVerifierExtension implements Serializable {
action.execute(contractDependency);
}
@Nested
public ContractRepository getContractRepository() {
return contractRepository;
}

View File

@@ -0,0 +1,455 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.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 java.io.File;
import java.util.Collection;
import javax.inject.Inject;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.Task;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
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.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.TaskAction;
import org.springframework.cloud.contract.stubrunner.ContractDownloader;
import org.springframework.cloud.contract.stubrunner.ScmStubDownloaderBuilder;
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.stubrunner.StubRunnerPropertyUtils;
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties;
import org.springframework.cloud.contract.verifier.converter.ToYamlConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
// 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
* @author Shannon Pamperl
* @since 1.0.2
*/
@CacheableTask
class ContractsCopyTask extends DefaultTask {
static final String TASK_NAME = "copyContracts";
static final String CONTRACTS = "contracts";
static final String BACKUP = "original";
// inputs
private final Property<Boolean> convertToYaml;
private final Property<Boolean> failOnNoContracts;
private final DirectoryProperty contractsDirectory;
private final Dependency contractDependency;
private final Repository contractRepository;
private final Property<StubRunnerProperties.StubsMode> contractsMode;
private final MapProperty<String, String> contractsProperties;
private final Property<String> contractsPath;
private final Property<Boolean> excludeBuildFolders;
/**
* @see ContractVerifierExtension#deleteStubsAfterTest
*
* This property will delete the temporary dependency or Git repository from
* which stubs were copied to this task's output directory.
*/
private final Property<Boolean> deleteStubsAfterTest;
// outputs
private final DirectoryProperty copiedContractsFolder;
private final DirectoryProperty backupContractsFolder;
@Inject
public ContractsCopyTask(ObjectFactory objects) {
convertToYaml = objects.property(Boolean.class);
failOnNoContracts = objects.property(Boolean.class);
contractsDirectory = objects.directoryProperty();
contractDependency = objects.newInstance(Dependency.class);
contractRepository = objects.newInstance(Repository.class);
contractsMode = objects.property(StubRunnerProperties.StubsMode.class);
contractsProperties = objects.mapProperty(String.class, String.class);
contractsPath = objects.property(String.class);
excludeBuildFolders = objects.property(Boolean.class);
deleteStubsAfterTest = objects.property(Boolean.class);
copiedContractsFolder = objects.directoryProperty();
backupContractsFolder = objects.directoryProperty();
this.getOutputs().upToDateWhen(task -> !(this.shouldDownloadContracts() && this.getContractDependency().toStubConfiguration().isVersionChanging()));
// Lambdas break build caching support
this.doFirst(new Action<Task>() {
@Override
public void execute(Task inner) {
String repositoryUrl = contractRepository.getRepositoryUrl().getOrNull();
if (repositoryUrl != null && ScmStubDownloaderBuilder.isProtocolAccepted(repositoryUrl)) {
String branch = StubRunnerPropertyUtils.getProperty(contractsProperties.get(), "git.branch");
branch = StringUtils.hasText(branch) ? branch : "master";
UsernamePasswordCredentialsProvider provider = null;
if (StringUtils.hasText(contractRepository.getUsername().get())) {
provider = new UsernamePasswordCredentialsProvider(contractRepository.getUsername().get(), contractRepository.getPassword().get());
}
try {
Collection<Ref> refs = Git.lsRemoteRepository()
.setRemote(repositoryUrl)
.setCredentialsProvider(provider)
.call();
for (Ref ref : refs) {
if (ref.getName().equals(branch) || ref.getName().equals("refs/heads/" + branch) || ref.getName().equals("refs/tags/" + branch)) {
contractsProperties.put("git.commit", ref.getObjectId().name());
}
}
} catch (GitAPIException e) {
ContractsCopyTask.this.getLogger().warn("Unable to determine git repository commit id");
}
}
}
});
}
@TaskAction
void sync() {
final File contractsDirectory;
final String antPattern;
if (shouldDownloadContracts()) {
DownloadedData downloadedData = downloadContracts();
contractsDirectory = downloadedData.downloadedContracts;
antPattern = downloadedData.inclusionProperties.getIncludedRootFolderAntPattern() + "*.*";
getLogger().info("Contracts got downloaded to [{}]", contractsDirectory);
}
else {
contractsDirectory = this.contractsDirectory.getAsFile().getOrNull();
antPattern = "**/";
}
getLogger().info("For project [{}] will use contracts provided in the folder [{}]", getProject().getName(), contractsDirectory);
final String contractsRepository = this.contractRepository.getRepositoryUrl().getOrElse("");
throwExceptionWhenFailOnNoContracts(contractsDirectory, contractsRepository);
final String slashSeparatedGroupId = getProject().getGroup().toString().replace(".", File.separator);
final String dotSeparatedAntPattern = antPattern.replace(slashSeparatedGroupId, getProject().getGroup().toString());
File output = copiedContractsFolder.get().getAsFile();
getLogger().info("Downloading and unpacking files from [{}] to [{}]. The inclusion ant patterns are [{}] and [{}]", contractsDirectory, output, antPattern, dotSeparatedAntPattern);
sync(contractsDirectory, antPattern, dotSeparatedAntPattern, excludeBuildFolders.get(), output);
if (convertToYaml.get()) {
convertContractsToYaml(contractsDirectory, antPattern, dotSeparatedAntPattern, output, excludeBuildFolders.get());
}
}
private void convertContractsToYaml(File file, String antPattern, String slashSeparatedAntPattern, File outputContractsFolder, boolean excludeBuildFolders) {
sync(file, antPattern, slashSeparatedAntPattern, excludeBuildFolders, backupContractsFolder.get().getAsFile());
ToYamlConverter.replaceContractWithYaml(outputContractsFolder);
getLogger().info("Replaced DSL files with their YAML representation at [{}]", outputContractsFolder);
}
private void sync(File file, String antPattern, String dotSeparatedAntPattern, boolean excludeBuildFolders, File outputContractsFolder) {
getProject().sync(spec -> {
spec.from(file);
// by default group id is slash separated...
spec.include(antPattern);
// ...we also want to allow dot separation
spec.include(dotSeparatedAntPattern);
if (excludeBuildFolders) {
spec.exclude("**/target/**", "**/build/**", "**/.mvn/**", "**/.gradle/**");
}
spec.into(outputContractsFolder);
});
}
private DownloadedData downloadContracts() {
String groupId = getProject().getGroup().toString();
String artifactId = getProject().getName();
getLogger().info("Project has group id [{}], artifact id [{}]", groupId, artifactId);
getLogger().info("For project [{}] Download dependency is provided - will download contract jars", artifactId);
getLogger().info("Contract dependency [{}]", contractDependency);
StubConfiguration configuration = contractDependency.toStubConfiguration();
getLogger().info("Got the following contract dependency to download [{}]", configuration);
getLogger().info("The contract dependency is a changing one [{}]", configuration.isVersionChanging());
final StubDownloader downloader = new StubDownloaderBuilderProvider().get(createStubRunnerOptions());
final ContractDownloader contractDownloader = new ContractDownloader(downloader, configuration,
contractsPath.getOrNull(), groupId, artifactId, getProject().getVersion().toString());
final File downloadedContracts = contractDownloader.unpackAndDownloadContracts();
final ContractDownloader.InclusionProperties inclusionProperties =
contractDownloader.createNewInclusionProperties(downloadedContracts);
// TODO: inclusionProperties.includedContracts is never used eventually. Review this:
return new DownloadedData(contractsSubDirIfPresent(downloadedContracts), inclusionProperties);
}
private void throwExceptionWhenFailOnNoContracts(@Nullable File file, String contractsRepository) {
if (StringUtils.hasText(contractsRepository)) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Contracts repository is set, will not throw an exception that the contracts are not found");
}
return;
}
if (failOnNoContracts.get() && (!file.exists() || file.listFiles().length == 0)) {
String path = file.getAbsolutePath();
throw new GradleException("Contracts could not be found: [" + path + "]\nPlease make sure that the contracts were defined, or set the [failOnNoContracts] flag to [false]");
}
}
private File contractsSubDirIfPresent(File contractsDirectory) {
File contracts = new File(contractsDirectory, "contracts");
if (contracts.exists()) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Contracts folder found [{}]", contracts);
}
contractsDirectory = contracts;
}
return contractsDirectory;
}
private class DownloadedData {
final File downloadedContracts;
final ContractDownloader.InclusionProperties inclusionProperties;
private DownloadedData(File downloadedContracts, ContractDownloader.InclusionProperties inclusionProperties) {
this.downloadedContracts = downloadedContracts;
this.inclusionProperties = inclusionProperties;
}
}
@Input
Property<Boolean> getConvertToYaml() {
return convertToYaml;
}
@Input
Property<Boolean> getFailOnNoContracts() {
return failOnNoContracts;
}
@InputDirectory
@PathSensitive(PathSensitivity.RELATIVE)
DirectoryProperty getContractsDirectory() {
return contractsDirectory;
}
@Nested
Dependency getContractDependency() {
return contractDependency;
}
static class Dependency {
private static final String LATEST_VERSION = "+";
private final Property<String> groupId;
private final Property<String> artifactId;
private final Property<String> version;
private final Property<String> classifier;
private final Property<String> stringNotation;
@Inject
public Dependency(ObjectFactory objects) {
groupId = objects.property(String.class);
artifactId = objects.property(String.class);
version = objects.property(String.class);
classifier = objects.property(String.class);
stringNotation = objects.property(String.class);
}
@Input
@Optional
Property<String> getGroupId() {
return groupId;
}
@Input
@Optional
Property<String> getArtifactId() {
return artifactId;
}
@Input
@Optional
Property<String> getVersion() {
return version;
}
@Input
@Optional
Property<String> getClassifier() {
return classifier;
}
@Input
@Optional
Property<String> getStringNotation() {
return stringNotation;
}
@Internal
StubConfiguration toStubConfiguration() {
String stringNotation = this.stringNotation.getOrNull();
if (StringUtils.hasText(stringNotation)) {
return new StubConfiguration(stringNotation);
}
String groupId = this.groupId.getOrNull();
String artifactId = this.artifactId.getOrNull();
String version = StringUtils.hasText(this.version.getOrNull()) ?
this.version.getOrNull() : LATEST_VERSION;
String classifier = this.classifier.getOrNull();
return new StubConfiguration(groupId, artifactId, version, classifier);
}
}
@Nested
Repository getContractRepository() {
return contractRepository;
}
static class Repository {
private final Property<String> repositoryUrl;
private final Property<String> username;
private final Property<String> password;
private final Property<String> proxyHost;
private final Property<Integer> proxyPort;
@Inject
public Repository(ObjectFactory objects) {
repositoryUrl = objects.property(String.class);
username = objects.property(String.class);
password = objects.property(String.class);
proxyHost = objects.property(String.class);
proxyPort = objects.property(Integer.class);
}
@Input
@Optional
Property<String> getRepositoryUrl() {
return repositoryUrl;
}
@Input
@Optional
Property<String> getUsername() {
return username;
}
@Input
@Optional
Property<String> getPassword() {
return password;
}
@Input
@Optional
Property<String> getProxyHost() {
return proxyHost;
}
@Input
@Optional
Property<Integer> getProxyPort() {
return proxyPort;
}
}
@Input
@Optional
Property<StubRunnerProperties.StubsMode> getContractsMode() {
return contractsMode;
}
@Input
MapProperty<String, String> getContractsProperties() {
return contractsProperties;
}
@Input
@Optional
Property<String> getContractsPath() {
return contractsPath;
}
@Input
Property<Boolean> getExcludeBuildFolders() {
return excludeBuildFolders;
}
@Input
Property<Boolean> getDeleteStubsAfterTest() {
return deleteStubsAfterTest;
}
@OutputDirectory
DirectoryProperty getCopiedContractsFolder() {
return copiedContractsFolder;
}
@Optional
@OutputDirectory
DirectoryProperty getBackupContractsFolder() {
return backupContractsFolder;
}
private boolean shouldDownloadContracts() {
return StringUtils.hasText(contractDependency.getArtifactId().getOrNull()) ||
StringUtils.hasText(contractDependency.getStringNotation().getOrNull()) ||
StringUtils.hasText(contractRepository.getRepositoryUrl().getOrNull());
}
private StubRunnerOptions createStubRunnerOptions() {
StubRunnerOptionsBuilder options = new StubRunnerOptionsBuilder()
.withOptions(StubRunnerOptions.fromSystemProps())
.withStubRepositoryRoot(contractRepository.repositoryUrl.getOrNull())
.withStubsMode(contractsMode.get())
.withUsername(contractRepository.username.getOrNull())
.withPassword(contractRepository.password.getOrNull())
.withDeleteStubsAfterTest(deleteStubsAfterTest.get())
.withProperties(contractsProperties.getOrNull())
.withFailOnNoStubs(failOnNoContracts.get());
if (contractRepository.proxyPort.isPresent()) {
options = options.withProxy(contractRepository.proxyHost.getOrNull(), contractRepository.proxyPort.get());
}
return options.build();
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.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 java.io.File;
import java.util.List;
import javax.inject.Inject;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.Directory;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.TaskAction;
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.
*
* @author Marcin Grzejszczak
* @author Anatoliy Balakirev
* @author Shannon Pamperl
* @since 2.0.0
*/
@CacheableTask
class GenerateClientStubsFromDslTask extends DefaultTask {
static final String TASK_NAME = "generateClientStubs";
static final String DEFAULT_MAPPINGS_FOLDER = "mappings";
private Property<Directory> contractsDslDir;
private ListProperty<String> excludedFiles;
private Property<Boolean> excludeBuildFolders;
private DirectoryProperty stubsOutputDir;
@Inject
public GenerateClientStubsFromDslTask(ObjectFactory objects) {
contractsDslDir = objects.directoryProperty();
excludedFiles = objects.listProperty(String.class);
excludeBuildFolders = objects.property(Boolean.class);
stubsOutputDir = objects.directoryProperty();
}
@TaskAction
void generate() {
File output = stubsOutputDir.get().getAsFile();
getLogger().info("Stubs output dir [{}]", output);
getLogger().info("Spring Cloud Contract Verifier Plugin: Invoking DSL to client stubs conversion");
getLogger().info("Contracts dir is [{}] output stubs dir is [{}]", contractsDslDir.get().getAsFile(), output);
List<String> excludedFiles = this.excludedFiles.get();
RecursiveFilesConverter converter = new RecursiveFilesConverter(output,
contractsDslDir.get().getAsFile(), excludedFiles, ".*", excludeBuildFolders.get());
converter.processFiles();
}
@InputDirectory
@PathSensitive(PathSensitivity.RELATIVE)
public Property<Directory> getContractsDslDir() {
return contractsDslDir;
}
@Input
public ListProperty<String> getExcludedFiles() {
return excludedFiles;
}
@Input
public Property<Boolean> getExcludeBuildFolders() {
return excludeBuildFolders;
}
@OutputDirectory
public Property<Directory> getStubsOutputDir() {
return stubsOutputDir;
}
}

View File

@@ -0,0 +1,236 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.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 org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
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.CacheableTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.TaskAction;
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 org.springframework.cloud.contract.verifier.config.TestMode;
import javax.inject.Inject;
import java.io.File;
import java.util.List;
/**
* Task used to generate server side tests
*
* @author Marcin Grzejszczak
* @author Anatoliy Balakirev
* @author Shannon Pamperl
* @since 1.0.0
*/
@CacheableTask
class GenerateServerTestsTask extends DefaultTask {
static final String TASK_NAME = "generateContractTests";
private final DirectoryProperty contractsDslDir;
private final Property<String> nameSuffixForTests;
private final Property<String> basePackageForTests;
private final Property<String> baseClassForTests;
private final Property<String> packageWithBaseClasses;
private final ListProperty<String> excludedFiles;
private final ListProperty<String> ignoredFiles;
private final ListProperty<String> includedFiles;
private final ListProperty<String> imports;
private final ListProperty<String> staticImports;
private final Property<TestMode> testMode;
private final Property<TestFramework> testFramework;
private final MapProperty<String, String> baseClassMappings;
private final Property<Boolean> assertJsonSize;
private final Property<Boolean> failOnInProgress;
private final DirectoryProperty generatedTestSourcesDir;
private final DirectoryProperty generatedTestResourcesDir;
@Inject
public GenerateServerTestsTask(ObjectFactory objects) {
this.contractsDslDir = objects.directoryProperty();
this.nameSuffixForTests = objects.property(String.class);
this.basePackageForTests = objects.property(String.class);
this.baseClassForTests = objects.property(String.class);
this.packageWithBaseClasses = objects.property(String.class);
this.excludedFiles = objects.listProperty(String.class);
this.ignoredFiles = objects.listProperty(String.class);
this.includedFiles = objects.listProperty(String.class);
this.imports = objects.listProperty(String.class);
this.staticImports = objects.listProperty(String.class);
this.testMode = objects.property(TestMode.class);
this.testFramework = objects.property(TestFramework.class);
this.baseClassMappings = objects.mapProperty(String.class, String.class);
this.assertJsonSize = objects.property(Boolean.class);
this.failOnInProgress = objects.property(Boolean.class);
this.generatedTestSourcesDir = objects.directoryProperty();
this.generatedTestResourcesDir = objects.directoryProperty();
}
@TaskAction
void generate() {
File generatedTestSources = this.generatedTestSourcesDir.get().getAsFile();
File generatedTestResources = this.generatedTestResourcesDir.get().getAsFile();
getLogger().info("Generated test sources dir [{}]", generatedTestSources);
getLogger().info("Generated test resources dir [{}]", generatedTestResources);
File contractsDslDir = this.contractsDslDir.get().getAsFile();
String includedContracts = ".*";
getLogger().info("Spring Cloud Contract Verifier Plugin: Invoking test sources generation");
getLogger().info("Contracts are unpacked to [{}]", contractsDslDir);
getLogger().info("Included contracts are [{}]", includedContracts);
try {
TestGenerator generator = new TestGenerator(toConfigProperties(contractsDslDir, includedContracts, generatedTestSources, generatedTestResources));
int generatedClasses = generator.generate();
getLogger().info("Generated {} test classes", generatedClasses);
}
catch (ContractVerifierException e) {
throw new GradleException("Spring Cloud Contract Verifier Plugin exception: " + e.getMessage(), e);
}
}
@InputDirectory
@PathSensitive(PathSensitivity.RELATIVE)
DirectoryProperty getContractsDslDir() {
return contractsDslDir;
}
@Input
@Optional
Property<String> getNameSuffixForTests() {
return nameSuffixForTests;
}
@Input
@Optional
Property<String> getBasePackageForTests() {
return basePackageForTests;
}
@Input
@Optional
Property<String> getBaseClassForTests() {
return baseClassForTests;
}
@Input
@Optional
Property<String> getPackageWithBaseClasses() {
return packageWithBaseClasses;
}
@Input
ListProperty<String> getExcludedFiles() {
return excludedFiles;
}
@Input
ListProperty<String> getIgnoredFiles() {
return ignoredFiles;
}
@Input
ListProperty<String> getIncludedFiles() {
return includedFiles;
}
@Input
ListProperty<String> getImports() {
return imports;
}
@Input
ListProperty<String> getStaticImports() {
return staticImports;
}
@Input
Property<TestMode> getTestMode() {
return testMode;
}
@Input
Property<TestFramework> getTestFramework() {
return testFramework;
}
@Input
MapProperty<String, String> getBaseClassMappings() {
return baseClassMappings;
}
@Input
Property<Boolean> getAssertJsonSize() {
return assertJsonSize;
}
@Input
Property<Boolean> getFailOnInProgress() {
return failOnInProgress;
}
@OutputDirectory
DirectoryProperty getGeneratedTestSourcesDir() {
return generatedTestSourcesDir;
}
@OutputDirectory
DirectoryProperty getGeneratedTestResourcesDir() {
return generatedTestResourcesDir;
}
private ContractVerifierConfigProperties toConfigProperties(File contractsDslDir,
String includedContracts, File generatedTestSources,
File generatedTestResources) {
List<String> excludedFiles = this.excludedFiles.get();
List<String> ignoredFiles = this.ignoredFiles.get();
List<String> includedFiles = this.includedFiles.get();
String[] imports = this.imports.get().toArray(new String[0]);
String[] staticImports = this.staticImports.get().toArray(new String[0]);
ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties();
properties.setIncludedContracts(includedContracts);
properties.setContractsDslDir(contractsDslDir);
properties.setNameSuffixForTests(nameSuffixForTests.getOrNull());
properties.setGeneratedTestSourcesDir(generatedTestSources);
properties.setGeneratedTestResourcesDir(generatedTestResources);
properties.setBasePackageForTests(basePackageForTests.getOrNull());
properties.setBaseClassForTests(baseClassForTests.getOrNull());
properties.setPackageWithBaseClasses(packageWithBaseClasses.getOrNull());
properties.setExcludedFiles(excludedFiles);
properties.setIgnoredFiles(ignoredFiles);
properties.setIncludedFiles(includedFiles);
properties.setImports(imports);
properties.setStaticImports(staticImports);
properties.setTestMode(testMode.get());
properties.setTestFramework(testFramework.get());
properties.setBaseClassMappings(baseClassMappings.get());
properties.setAssertJsonSize(assertJsonSize.get());
properties.setFailOnInProgress(failOnInProgress.get());
return properties;
}
}

View File

@@ -0,0 +1,196 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.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 javax.inject.Inject;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
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.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
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.StubRunnerOptionsBuilder;
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties;
import org.springframework.util.StringUtils;
/**
* For SCM based repositories will copy the generated stubs
* to the cloned repo with contracts and stubs. Will also
* commit the changes and push them to origin.
*
* NOTE: starting with 2.3.0.RELEASE the <code>customize{}</code> closure previously used for
* {@link PublishStubsToScmTask} customisation is no longer available. The settings should be applied directly
* within the <code>publishStubsToScm</code> closure as in the example above.
*
* @author Marcin Grzejszczak
* @author Anatoliy Balakirev
* @author Shannon Pamperl
* @since 2.0.0
*/
class PublishStubsToScmTask extends DefaultTask {
static final String TASK_NAME = "publishStubsToScm";
private final Repository contractRepository;
private final Property<StubRunnerProperties.StubsMode> contractsMode;
/**
* @see ContractVerifierExtension#deleteStubsAfterTest
*
* This property will delete the Git repository where the input
* stubs to this task have been committed.
*/
private final Property<Boolean> deleteStubsAfterTest;
private final Property<Boolean> failOnNoContracts;
private final MapProperty<String, String> contractsProperties;
private final DirectoryProperty stubsDir;
@Inject
public PublishStubsToScmTask(ObjectFactory objects) {
this.contractRepository = objects.newInstance(Repository.class);
this.contractsMode = objects.property(StubRunnerProperties.StubsMode.class);
this.deleteStubsAfterTest = objects.property(Boolean.class);
this.failOnNoContracts = objects.property(Boolean.class);
this.contractsProperties = objects.mapProperty(String.class, String.class);
this.stubsDir = objects.directoryProperty();
this.onlyIf(task -> {
String contractRepoUrl = contractRepository.repositoryUrl.getOrElse("");
if (StringUtils.isEmpty(contractRepoUrl) || !ScmStubDownloaderBuilder.isProtocolAccepted(contractRepoUrl)) {
getLogger().warn("Skipping pushing stubs to scm since your contracts repository URL [{}] doesn't match any of the accepted protocols for SCM stub downloader", contractRepoUrl);
return false;
}
return true;
});
}
@TaskAction
void publishStubsToScm() {
String projectName = getProject().getGroup().toString() + ":" + getProject().getName() + ":" + getProject().getVersion().toString();
getLogger().info("Pushing Stubs to SCM for project [{}]", projectName);
StubRunnerOptions stubRunnerOptions = createStubRunnerOptions();
new ContractProjectUpdater(stubRunnerOptions).updateContractProject(projectName, stubsDir.get().getAsFile().toPath());
}
@Nested
Repository getContractRepository() {
return contractRepository;
}
static class Repository {
private final Property<String> repositoryUrl;
private final Property<String> username;
private final Property<String> password;
private final Property<Integer> proxyPort;
private final Property<String> proxyHost;
@Inject
public Repository(ObjectFactory objects) {
this.repositoryUrl = objects.property(String.class);
this.username = objects.property(String.class);
this.password = objects.property(String.class);
this.proxyHost = objects.property(String.class);
this.proxyPort = objects.property(Integer.class);
}
@Input
@Optional
Property<String> getRepositoryUrl() {
return repositoryUrl;
}
@Input
@Optional
Property<String> getUsername() {
return username;
}
@Input
@Optional
Property<String> getPassword() {
return password;
}
@Input
@Optional
Property<Integer> getProxyPort() {
return proxyPort;
}
@Input
@Optional
Property<String> getProxyHost() {
return proxyHost;
}
}
@Input
Property<StubRunnerProperties.StubsMode> getContractsMode() {
return contractsMode;
}
@Input
Property<Boolean> getDeleteStubsAfterTest() {
return deleteStubsAfterTest;
}
@Input
Property<Boolean> getFailOnNoContracts() {
return failOnNoContracts;
}
@Input
MapProperty<String, String> getContractsProperties() {
return contractsProperties;
}
@InputDirectory
@PathSensitive(PathSensitivity.RELATIVE)
DirectoryProperty getStubsDir() {
return stubsDir;
}
private StubRunnerOptions createStubRunnerOptions() {
StubRunnerOptionsBuilder options = new StubRunnerOptionsBuilder()
.withOptions(StubRunnerOptions.fromSystemProps())
.withStubRepositoryRoot(contractRepository.repositoryUrl.getOrNull())
.withStubsMode(contractsMode.get())
.withUsername(contractRepository.username.getOrNull())
.withPassword(contractRepository.password.getOrNull())
.withDeleteStubsAfterTest(deleteStubsAfterTest.get())
.withProperties(contractsProperties.getOrNull())
.withFailOnNoStubs(failOnNoContracts.get());
if (contractRepository.proxyPort.isPresent()) {
options = options.withProxy(contractRepository.proxyHost.getOrNull(), contractRepository.proxyPort.get());
}
return options.build();
}
}

View File

@@ -0,0 +1,373 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.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 java.io.File;
import javax.annotation.Nullable;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.file.Directory;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.FileCollection;
import org.gradle.api.internal.HasConvention;
import org.gradle.api.plugins.GroovyPlugin;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.publish.PublishingExtension;
import org.gradle.api.publish.maven.MavenPublication;
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
import org.gradle.api.tasks.GroovySourceSet;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.SourceSetOutput;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.bundling.Jar;
import org.gradle.api.tasks.testing.Test;
import org.springframework.cloud.contract.verifier.config.TestFramework;
/**
* Gradle plugin for Spring Cloud Contract Verifier that from the DSL contract can
* <ul>
* <li>generate tests</li>
* <li>generate stubs</li>
* </ul>
*
* @author Jakub Kubrynski, codearte.io
* @author Marcin Grzejszczak
* @author Anatoliy Balakirev
* @author Shannon Pamperl
*
* @since 1.0.0
*/
public class SpringCloudContractVerifierGradlePlugin implements Plugin<Project> {
private static final String GROUP_NAME = "Verification";
private static final String EXTENSION_NAME = "contracts";
private static final String CONTRACT_TEST_SOURCE_SET_NAME = "contractTest";
private static final String CONTRACT_TEST_COMPILE_ONLY_CONFIGURATION_NAME = "contractTestCompileOnly";
private static final String CONTRACT_TEST_IMPLEMENTATION_CONFIGURATION_NAME = "contractTestImplementation";
private static final String CONTRACT_TEST_RUNTIME_ONLY_CONFIGURATION_NAME = "contractTestRuntimeOnly";
private static final String VERIFIER_STUBS_JAR_TASK_NAME = "verifierStubsJar";
private static final String CONTRACT_TEST_TASK_NAME = "contractTest";
private Project project;
@Override
public void apply(Project project) {
this.project = project;
project.getPlugins().apply(JavaPlugin.class);
ContractVerifierExtension extension = project.getExtensions().create(EXTENSION_NAME, ContractVerifierExtension.class);
JavaPluginConvention javaConvention = project.getConvention().getPlugin(JavaPluginConvention.class);
SourceSet contractTestSourceSet = configureSourceSets(extension, javaConvention);
configureConfigurations();
registerContractTestTask(contractTestSourceSet);
TaskProvider<ContractsCopyTask> copyContracts = createAndConfigureCopyContractsTask(extension);
TaskProvider<GenerateClientStubsFromDslTask> generateClientStubs = createAndConfigureGenerateClientStubs(extension, copyContracts);
createAndConfigureStubsJarTasks(extension, generateClientStubs);
createGenerateTestsTask(extension, contractTestSourceSet, copyContracts);
createAndConfigurePublishStubsToScmTask(extension, generateClientStubs);
project.afterEvaluate(inner -> {
DirectoryProperty generatedTestSourcesDir = extension.getGeneratedTestSourcesDir();
if (generatedTestSourcesDir.isPresent()) {
if (extension.getTestFramework().get() == TestFramework.SPOCK) {
project.getPlugins().withType(GroovyPlugin.class, groovyPlugin -> {
GroovySourceSet groovy = ((HasConvention) contractTestSourceSet).getConvention().getPlugin(GroovySourceSet.class);
groovy.getGroovy().srcDirs(generatedTestSourcesDir);
});
} else {
contractTestSourceSet.getJava().srcDirs(generatedTestSourcesDir);
}
}
});
}
private SourceSet configureSourceSets(ContractVerifierExtension extension, JavaPluginConvention javaConvention) {
SourceSetContainer sourceSets = javaConvention.getSourceSets();
SourceSet contractTest = sourceSets.create(CONTRACT_TEST_SOURCE_SET_NAME);
contractTest.getJava().srcDirs(extension.getGeneratedTestJavaSourcesDir());
project.getPlugins().withType(GroovyPlugin.class, groovyPlugin -> {
GroovySourceSet groovy = ((HasConvention) contractTest).getConvention().getPlugin(GroovySourceSet.class);
groovy.getGroovy().srcDirs(extension.getGeneratedTestGroovySourcesDir());
});
contractTest.getResources().srcDirs(extension.getGeneratedTestResourcesDir());
SourceSetOutput mainOutput = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput();
SourceSetOutput testOutput = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME).getOutput();
FileCollection compileClasspath = contractTest.getCompileClasspath();
contractTest.setCompileClasspath(compileClasspath.plus(mainOutput).plus(testOutput));
FileCollection runtimeClasspath = contractTest.getRuntimeClasspath();
contractTest.setRuntimeClasspath(runtimeClasspath.plus(mainOutput).plus(testOutput));
return contractTest;
}
private void configureConfigurations() {
ConfigurationContainer configurations = project.getConfigurations();
Configuration testCompileOnly = configurations.getByName(JavaPlugin.TEST_COMPILE_ONLY_CONFIGURATION_NAME);
Configuration testImplementation = configurations.getByName(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME);
Configuration testRuntimeOnly = configurations.getByName(JavaPlugin.TEST_RUNTIME_ONLY_CONFIGURATION_NAME);
Configuration contractTestCompileOnly = configurations.getByName(CONTRACT_TEST_COMPILE_ONLY_CONFIGURATION_NAME);
Configuration contractTestImplementation = configurations.getByName(CONTRACT_TEST_IMPLEMENTATION_CONFIGURATION_NAME);
Configuration contractTestRuntimeOnly = configurations.getByName(CONTRACT_TEST_RUNTIME_ONLY_CONFIGURATION_NAME);
contractTestCompileOnly.extendsFrom(testCompileOnly);
contractTestImplementation.extendsFrom(testImplementation);
contractTestRuntimeOnly.extendsFrom(testRuntimeOnly);
}
private void registerContractTestTask(SourceSet contractTestSourceSet) {
TaskProvider<Test> contractTestTask = project.getTasks().register(CONTRACT_TEST_TASK_NAME, Test.class, contractTest -> {
contractTest.setDescription("Runs the contract tests");
contractTest.setGroup(GROUP_NAME);
contractTest.setTestClassesDirs(contractTestSourceSet.getOutput().getClassesDirs());
contractTest.setClasspath(contractTestSourceSet.getRuntimeClasspath());
contractTest.mustRunAfter(project.getTasks().named(JavaPlugin.TEST_TASK_NAME));
});
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, check -> check.dependsOn(contractTestTask));
}
private void createGenerateTestsTask(ContractVerifierExtension extension, SourceSet contractTestSourceSet, TaskProvider<ContractsCopyTask> copyContracts) {
TaskProvider<GenerateServerTestsTask> task = project.getTasks().register(GenerateServerTestsTask.TASK_NAME, GenerateServerTestsTask.class);
task.configure(generateServerTestsTask -> {
generateServerTestsTask.setDescription("Generate server tests from the contracts");
generateServerTestsTask.setGroup(GROUP_NAME);
generateServerTestsTask.getContractsDslDir().convention(copyContracts.flatMap(ContractsCopyTask::getCopiedContractsFolder));
generateServerTestsTask.getNameSuffixForTests().convention(extension.getNameSuffixForTests());
generateServerTestsTask.getBasePackageForTests().convention(extension.getBasePackageForTests());
generateServerTestsTask.getBaseClassForTests().convention(extension.getBaseClassForTests());
generateServerTestsTask.getPackageWithBaseClasses().convention(extension.getPackageWithBaseClasses());
generateServerTestsTask.getExcludedFiles().convention(extension.getExcludedFiles());
generateServerTestsTask.getIgnoredFiles().convention(extension.getIgnoredFiles());
generateServerTestsTask.getIncludedFiles().convention(extension.getIncludedFiles());
generateServerTestsTask.getImports().convention(extension.getImports());
generateServerTestsTask.getStaticImports().convention(extension.getStaticImports());
generateServerTestsTask.getTestMode().convention(extension.getTestMode());
generateServerTestsTask.getTestFramework().convention(extension.getTestFramework());
generateServerTestsTask.getBaseClassMappings().convention(extension.getBaseClassMappings().getBaseClassMappings());
generateServerTestsTask.getAssertJsonSize().convention(extension.getAssertJsonSize());
generateServerTestsTask.getFailOnInProgress().convention(extension.getFailOnInProgress());
generateServerTestsTask.getGeneratedTestSourcesDir().convention(extension.getTestFramework().flatMap(testFramework -> {
Property<Directory> correctSourceSetDir;
if (testFramework == TestFramework.SPOCK) {
correctSourceSetDir = extension.getGeneratedTestGroovySourcesDir();
} else {
correctSourceSetDir = extension.getGeneratedTestJavaSourcesDir();
}
return extension.getGeneratedTestSourcesDir().orElse(correctSourceSetDir);
}));
generateServerTestsTask.getGeneratedTestResourcesDir().convention(extension.getGeneratedTestResourcesDir());
generateServerTestsTask.dependsOn(copyContracts);
});
project.getTasks().named(contractTestSourceSet.getCompileJavaTaskName(), compileContractTestJava -> {
compileContractTestJava.dependsOn(task);
});
project.getPlugins().withType(GroovyPlugin.class, groovyPlugin -> {
project.getTasks().named(contractTestSourceSet.getCompileTaskName("groovy"), compileContractTestGroovy -> {
compileContractTestGroovy.dependsOn(task);
});
});
}
private void createAndConfigurePublishStubsToScmTask(ContractVerifierExtension extension,
TaskProvider<GenerateClientStubsFromDslTask> generateClientStubs) {
TaskProvider<PublishStubsToScmTask> task = project.getTasks().register(PublishStubsToScmTask.TASK_NAME, PublishStubsToScmTask.class);
task.configure(publishStubsToScmTask -> {
publishStubsToScmTask.setDescription("The generated stubs get committed to the SCM repo and pushed to origin");
publishStubsToScmTask.setGroup(GROUP_NAME);
ContractVerifierExtension.ContractRepository stubs = extension.getPublishStubsToScm().getContractRepository();
ContractVerifierExtension.ContractRepository original = extension.getContractRepository();
publishStubsToScmTask.getContractRepository().getRepositoryUrl().convention(stubs.getRepositoryUrl().orElse(original.getRepositoryUrl()));
publishStubsToScmTask.getContractRepository().getUsername().convention(stubs.getUsername().orElse(original.getUsername()));
publishStubsToScmTask.getContractRepository().getPassword().convention(stubs.getPassword().orElse(original.getPassword()));
publishStubsToScmTask.getContractRepository().getProxyHost().convention(stubs.getProxyHost().orElse(original.getProxyHost()));
publishStubsToScmTask.getContractRepository().getProxyPort().convention(stubs.getProxyPort().orElse(original.getProxyPort()));
publishStubsToScmTask.getContractsMode().convention(extension.getContractsMode());
publishStubsToScmTask.getDeleteStubsAfterTest().convention(extension.getDeleteStubsAfterTest());
publishStubsToScmTask.getFailOnNoContracts().convention(extension.getFailOnNoContracts());
publishStubsToScmTask.getContractsProperties().convention(extension.getContractsProperties());
publishStubsToScmTask.getStubsDir().convention(generateClientStubs.flatMap(GenerateClientStubsFromDslTask::getStubsOutputDir));
publishStubsToScmTask.dependsOn(generateClientStubs);
});
}
private TaskProvider<GenerateClientStubsFromDslTask> createAndConfigureGenerateClientStubs(ContractVerifierExtension extension,
TaskProvider<ContractsCopyTask> copyContracts) {
TaskProvider<GenerateClientStubsFromDslTask> task = project.getTasks().register(GenerateClientStubsFromDslTask.TASK_NAME, GenerateClientStubsFromDslTask.class, generateClientStubs -> {
generateClientStubs.setGroup(GROUP_NAME);
generateClientStubs.setDescription("Generate client stubs from the contracts");
generateClientStubs.getContractsDslDir().convention(copyContracts.flatMap(ContractsCopyTask::getCopiedContractsFolder));
generateClientStubs.getExcludedFiles().convention(extension.getExcludedFiles());
generateClientStubs.getExcludeBuildFolders().convention(extension.getExcludeBuildFolders());
generateClientStubs.getStubsOutputDir().convention(extension.getStubsOutputDir().dir(buildRootPath(GenerateClientStubsFromDslTask.DEFAULT_MAPPINGS_FOLDER)));
generateClientStubs.dependsOn(copyContracts);
});
return task;
}
private void createAndConfigureStubsJarTasks(ContractVerifierExtension extension,
TaskProvider<GenerateClientStubsFromDslTask> generateClientStubs) {
TaskProvider<Jar> verifierStubsJar = project.getTasks().register(VERIFIER_STUBS_JAR_TASK_NAME, Jar.class);
verifierStubsJar.configure(stubsJar -> {
stubsJar.setDescription("Creates the stubs JAR task");
stubsJar.setGroup(GROUP_NAME);
stubsJar.getArchiveBaseName().convention(project.provider(project::getName));
stubsJar.getArchiveClassifier().convention(extension.getStubsSuffix());
stubsJar.from(extension.getStubsOutputDir());
stubsJar.dependsOn(generateClientStubs);
});
project.artifacts(artifactHandler -> artifactHandler.add("archives", verifierStubsJar));
createAndConfigureMavenPublishPlugin(verifierStubsJar, extension);
}
@Deprecated
private void createAndConfigureMavenPublishPlugin(TaskProvider<Jar> stubsTask, ContractVerifierExtension extension) {
if (!classIsOnClasspath("org.gradle.api.publish.maven.plugins.MavenPublishPlugin")) {
project.getLogger().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(inner -> {
project.getLogger().debug("Spring Cloud Contract Verifier Plugin: Generating default publication");
if (extension.getDisableStubPublication().get()) {
project.getLogger().info("You've switched off the stub publication - won't add default publication");
return;
}
project.getPlugins().withType(MavenPublishPlugin.class, publishingPlugin -> {
PublishingExtension publishingExtension = project.getExtensions().findByType(PublishingExtension.class);
if (hasStubsPublication(publishingExtension)) {
project.getLogger().info("Spring Cloud Contract Verifier Plugin: Stubs publication was present - won't create a new one. Remember about passing stubs as artifact");
}
else {
project.getLogger().debug("Spring Cloud Contract Verifier Plugin: Stubs publication is not present - will create one");
setPublications(publishingExtension, stubsTask);
}
});
});
}
@Deprecated
private void setPublications(PublishingExtension publishingExtension, TaskProvider<Jar> stubsTask) {
project.getLogger().warn("Spring Cloud Contract Verifier Plugin: Creating stubs publication is deprecated");
publishingExtension.publications(publicationsContainer -> {
publicationsContainer.create("stubs", MavenPublication.class, stubsPublication -> {
stubsPublication.setArtifactId(project.getName());
stubsPublication.artifact(stubsTask.get());
});
});
}
private @Nullable TaskProvider<Jar> stubsTask() {
try {
return project.getTasks().named(VERIFIER_STUBS_JAR_TASK_NAME, Jar.class);
}
catch (Exception e) {
return null;
}
}
@Deprecated
private boolean hasStubsPublication(PublishingExtension publishingExtension) {
try {
return publishingExtension.getPublications().getByName("stubs") != null;
}
catch (Exception e) {
return false;
}
}
private TaskProvider<ContractsCopyTask> createAndConfigureCopyContractsTask(ContractVerifierExtension extension) {
TaskProvider<ContractsCopyTask> task = project.getTasks().register(ContractsCopyTask.TASK_NAME, ContractsCopyTask.class, contractsCopyTask -> {
contractsCopyTask.setGroup(GROUP_NAME);
contractsCopyTask.setDescription("Copies contracts to the output folder");
contractsCopyTask.getConvertToYaml().convention(extension.getConvertToYaml());
contractsCopyTask.getFailOnNoContracts().convention(extension.getFailOnNoContracts());
contractsCopyTask.getContractsDirectory().convention(extension.getContractsDslDir());
contractsCopyTask.getContractDependency().getGroupId().convention(extension.getContractDependency().getGroupId());
contractsCopyTask.getContractDependency().getArtifactId().convention(extension.getContractDependency().getArtifactId());
contractsCopyTask.getContractDependency().getVersion().convention(extension.getContractDependency().getVersion());
contractsCopyTask.getContractDependency().getClassifier().convention(extension.getContractDependency().getClassifier());
contractsCopyTask.getContractDependency().getStringNotation().convention(extension.getContractDependency().getStringNotation());
contractsCopyTask.getContractRepository().getRepositoryUrl().convention(extension.getContractRepository().getRepositoryUrl());
contractsCopyTask.getContractRepository().getUsername().convention(extension.getContractRepository().getUsername());
contractsCopyTask.getContractRepository().getPassword().convention(extension.getContractRepository().getPassword());
contractsCopyTask.getContractRepository().getProxyHost().convention(extension.getContractRepository().getProxyHost());
contractsCopyTask.getContractRepository().getProxyPort().convention(extension.getContractRepository().getProxyPort());
contractsCopyTask.getContractsMode().convention(extension.getContractsMode());
contractsCopyTask.getContractsProperties().convention(extension.getContractsProperties());
contractsCopyTask.getContractsPath().convention(extension.getContractsPath());
contractsCopyTask.getExcludeBuildFolders().convention(extension.getExcludeBuildFolders());
contractsCopyTask.getDeleteStubsAfterTest().convention(extension.getDeleteStubsAfterTest());
contractsCopyTask.getCopiedContractsFolder().convention(extension.getStubsOutputDir().dir(buildRootPath(ContractsCopyTask.CONTRACTS)));
contractsCopyTask.getBackupContractsFolder().convention(extension.getStubsOutputDir().dir(buildRootPath(ContractsCopyTask.BACKUP)));
});
return task;
}
@Deprecated
private boolean classIsOnClasspath(String className) {
try {
Class.forName(className);
return true;
}
catch (Exception e) {
project.getLogger().debug("Maven Publish Plugin is not available");
}
return false;
}
private Provider<String> buildRootPath(String path) {
return project.provider(() -> {
StringBuilder builder = new StringBuilder();
builder.append("META-INF")
.append(File.separator)
.append(project.getGroup())
.append(File.separator)
.append(project.getName())
.append(File.separator)
.append(project.getVersion())
.append(File.separator)
.append(path);
return builder.toString();
});
}
}

View File

@@ -0,0 +1,4 @@
@NonNullApi
package org.springframework.cloud.contract.verifier.plugin;
import org.springframework.lang.NonNullApi;

View File

@@ -145,10 +145,16 @@ abstract class ContractVerifierIntegrationSpec extends Specification {
rootFile.eachFileRecurse { File file ->
try {
if (file.isFile() && file.name.endsWith('jar')) {
new ZipFile(file).entries().each {
if (it.name.endsWith('.groovy')) {
containsGroovyFiles = true
ZipFile zipFile;
try {
zipFile = new ZipFile(file)
zipFile.entries().each {
if (it.name.endsWith('.groovy')) {
containsGroovyFiles = true
}
}
} finally {
zipFile?.close()
}
}
}

View File

@@ -17,12 +17,18 @@
package org.springframework.cloud.contract.verifier.plugin
import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.Directory
import org.gradle.api.internal.project.DefaultProject
import org.gradle.api.plugins.GroovyPlugin
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.publish.PublicationContainer
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin
import org.gradle.api.tasks.SourceSet
import org.gradle.testfixtures.ProjectBuilder
import org.springframework.cloud.contract.verifier.config.TestFramework
import spock.lang.Specification
class ContractVerifierSpec extends Specification {
@@ -36,9 +42,9 @@ class ContractVerifierSpec extends Specification {
project.plugins.apply(SpringCloudContractVerifierGradlePlugin)
}
def "should apply groovy plugin"() {
def "should apply java plugin"() {
expect:
project.plugins.hasPlugin(GroovyPlugin)
project.plugins.hasPlugin(JavaPlugin)
}
def "should create contracts extension"() {
@@ -46,24 +52,106 @@ class ContractVerifierSpec extends Specification {
project.extensions.findByType(ContractVerifierExtension) != null
}
def "should create generateContractTests task"() {
def "should create a test sourceset with java sources"() {
given:
ContractVerifierExtension extension = project.extensions.getByType(ContractVerifierExtension)
Directory projectDir = project.layout.projectDirectory
SourceSet contractTest = project.convention.getPlugin(JavaPluginConvention).getSourceSets().getByName("contractTest")
expect:
project.tasks.named("generateContractTests") != null
contractTest != null
contractTest.java.srcDirs.contains(projectDir.dir("src/contractTest/java").asFile)
contractTest.java.srcDirs.contains(extension.generatedTestJavaSourcesDir.get().asFile)
contractTest.resources.srcDirs.contains(projectDir.dir("src/contractTest/resources").asFile)
contractTest.resources.srcDirs.contains(extension.generatedTestResourcesDir.get().asFile)
}
def "should configure generateContractTests task as a dependency of the check task"() {
def "should create a test sourceset with groovy sources, if the groovy plugin is present"() {
given:
project.plugins.apply(GroovyPlugin)
ContractVerifierExtension extension = project.extensions.getByType(ContractVerifierExtension)
Directory projectDir = project.layout.projectDirectory
SourceSet contractTest = project.convention.getPlugin(JavaPluginConvention).getSourceSets().getByName("contractTest")
expect:
project.tasks.check.getDependsOn().contains(project.tasks.named("generateContractTests"))
contractTest != null
contractTest.java.srcDirs.contains(projectDir.dir("src/contractTest/java").asFile)
contractTest.java.srcDirs.contains(extension.generatedTestJavaSourcesDir.get().asFile)
contractTest.groovy.srcDirs.contains(projectDir.dir('src/contractTest/groovy').asFile)
contractTest.groovy.srcDirs.contains(extension.generatedTestGroovySourcesDir.get().asFile)
contractTest.resources.srcDirs.contains(projectDir.dir("src/contractTest/resources").asFile)
contractTest.resources.srcDirs.contains(extension.generatedTestResourcesDir.get().asFile)
}
def "should setup dependency configurations"() {
given:
Configuration contractTestCompileOnly = project.configurations.contractTestCompileOnly
Configuration contractTestImplementation = project.configurations.contractTestImplementation
Configuration contractTestRuntimeOnly = project.configurations.contractTestRuntimeOnly
expect:
contractTestCompileOnly != null
contractTestCompileOnly.extendsFrom.contains(project.configurations.testCompileOnly)
contractTestImplementation != null
contractTestImplementation.extendsFrom.contains(project.configurations.testImplementation)
contractTestRuntimeOnly != null
contractTestRuntimeOnly.extendsFrom.contains(project.configurations.testRuntimeOnly)
}
def "should create contract test task"() {
expect:
project.tasks.named("contractTest").get() != null
}
def "should create generateContractTests task"() {
expect:
project.tasks.named("generateContractTests").get() != null
}
def "should configure generateContractTests task as a dependency of the compileContractTestJava task"() {
expect:
project.tasks.compileContractTestJava.getDependsOn().contains(project.tasks.named("generateContractTests"))
project.tasks.findByName("compileContractTestGroovy") == null
}
def "should configure generateContractTests task as a dependency of the compileContractTestGroovy task"() {
given:
project.plugins.apply(GroovyPlugin)
expect:
project.tasks.compileContractTestJava.getDependsOn().contains(project.tasks.named("generateContractTests"))
project.tasks.compileContractTestGroovy.getDependsOn().contains(project.tasks.named("generateContractTests"))
}
def "should configure generatedTestSourcesDir with the appropriate directories"() {
when:
ContractVerifierExtension extension = project.extensions.findByType(ContractVerifierExtension)
GenerateServerTestsTask generateServerTestsTask = project.tasks.getByName("generateContractTests") as GenerateServerTestsTask
then:
generateServerTestsTask.generatedTestSourcesDir.get().asFile == extension.generatedTestJavaSourcesDir.get().asFile
and:
extension.testFramework.set(TestFramework.SPOCK)
then:
generateServerTestsTask.generatedTestSourcesDir.get().asFile == extension.generatedTestGroovySourcesDir.get().asFile
and:
extension.generatedTestSourcesDir.set(project.file("src/random"))
then:
generateServerTestsTask.generatedTestSourcesDir.get().asFile == extension.generatedTestSourcesDir.get().asFile
}
def "should create generateClientStubs task"() {
expect:
project.tasks.named("generateClientStubs") != null
project.tasks.named("generateClientStubs").get() != null
}
def "should create verifierStubsJar task"() {
expect:
project.tasks.named("verifierStubsJar") != null
project.tasks.named("verifierStubsJar").get() != null
}
def "should configure generateClientStubs task as a dependency of the verifierStubsJar task"() {
@@ -78,12 +166,12 @@ class ContractVerifierSpec extends Specification {
def "should create copyContracts task"() {
expect:
project.tasks.named("copyContracts") != null
project.tasks.named("copyContracts").get() != null
}
def "should configure copyContracts task as a dependency of the verifierStubsJar task"() {
expect:
project.tasks.verifierStubsJar.getDependsOn().contains(project.tasks.named("copyContracts"))
project.tasks.verifierStubsJar.getDependsOn().contains(project.tasks.named("generateClientStubs"))
}
/**
@@ -132,4 +220,43 @@ class ContractVerifierSpec extends Specification {
expect:
extension
}
def "should property merge scm repository settings for publishing stubs to scm"() {
given:
project.plugins.apply(SpringCloudContractVerifierGradlePlugin)
ContractVerifierExtension extension = project.extensions.findByType(ContractVerifierExtension)
PublishStubsToScmTask task = project.tasks.findByName(PublishStubsToScmTask.TASK_NAME)
when:
extension.contractRepository.with {
repositoryUrl = "https://git.example.com"
username = "username"
password = "password"
proxyHost = "host"
proxyPort = 8080
}
then:
task.contractRepository.repositoryUrl.get() == "https://git.example.com"
task.contractRepository.username.get() == "username"
task.contractRepository.password.get() == "password"
task.contractRepository.proxyHost.get() == "host"
task.contractRepository.proxyPort.get() == 8080
and:
extension.publishStubsToScm.contractRepository.with {
repositoryUrl = "https://git2.example.com"
username = "username2"
password = "password2"
proxyHost = "host2"
proxyPort = 8081
}
then:
task.contractRepository.repositoryUrl.get() == "https://git2.example.com"
task.contractRepository.username.get() == "username2"
task.contractRepository.password.get() == "password2"
task.contractRepository.proxyHost.get() == "host2"
task.contractRepository.proxyPort.get() == 8081
}
}

View File

@@ -1,105 +0,0 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.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 org.gradle.api.internal.provider.DefaultProperty
import org.gradle.api.internal.provider.PropertyHost
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.internal.service.scopes.ProjectBackedPropertyHost
import spock.lang.Specification
import org.springframework.cloud.contract.stubrunner.StubConfiguration
/**
* @author Marcin Grzejszczak
* @author Anatoliy Balakirev
*/
class GradleContractsDownloaderHelperSpec extends Specification {
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)]
}
def "should parse dependency via string notation"() {
given:
String stringNotation = "com.example:foo:1.0.0:stubs"
ContractVerifierExtension.Dependency dep = new ContractVerifierExtension.Dependency(objectFactory)
dep.stringNotation.set(stringNotation)
when:
StubConfiguration stubConfig = GradleContractsDownloaderHelper.stubConfiguration(dep)
then:
stubConfig.groupId == "com.example"
stubConfig.artifactId == "foo"
stubConfig.version == "1.0.0"
stubConfig.classifier == "stubs"
}
def "should parse dependency via direct setting"() {
given:
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 = GradleContractsDownloaderHelper.stubConfiguration(dep)
then:
stubConfig.groupId == "com.example"
stubConfig.artifactId == "foo"
stubConfig.version == "1.0.0"
stubConfig.classifier == "stubs"
}
def "should parse dependency via string notation with methods"() {
given:
String stringNotation = "com.example:foo:1.0.0:stubs"
ContractVerifierExtension.Dependency dep = new ContractVerifierExtension.Dependency(objectFactory)
dep.stringNotation.set(stringNotation)
when:
StubConfiguration stubConfig = GradleContractsDownloaderHelper.stubConfiguration(dep)
then:
stubConfig.groupId == "com.example"
stubConfig.artifactId == "foo"
stubConfig.version == "1.0.0"
stubConfig.classifier == "stubs"
}
def "should parse dependency via direct setting with methods"() {
given:
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 = GradleContractsDownloaderHelper.stubConfiguration(dep)
then:
stubConfig.groupId == "com.example"
stubConfig.artifactId == "foo"
stubConfig.version == "1.0.0"
stubConfig.classifier == "stubs"
}
// Have to use this internal property impl here. Is there some better way?
public <T> Property<T> prop(Class<T> aClass) {
return new DefaultProperty(Stub(PropertyHost), aClass)
}
}

View File

@@ -16,6 +16,8 @@
package org.springframework.cloud.contract.verifier.plugin
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.TaskOutcome
import spock.lang.Ignore
import spock.lang.Stepwise
@@ -35,6 +37,9 @@ class ScenarioProjectKotlinSpec extends ContractVerifierKotlinIntegrationSpec {
expect:
runTasksSuccessfully(checkAndPublishToMavenLocal())
jarContainsContractVerifierContracts('fraudDetectionService/build/libs')
BuildResult result = run("check", "--info", "--stacktrace")
result.task(":fraudDetectionService:check").outcome == TaskOutcome.UP_TO_DATE
result.task(":loanApplicationService:check").outcome == TaskOutcome.UP_TO_DATE
}
def "should pass basic flow for JUnit"() {
@@ -44,6 +49,9 @@ class ScenarioProjectKotlinSpec extends ContractVerifierKotlinIntegrationSpec {
switchToJunitTestFramework()
runTasksSuccessfully(checkAndPublishToMavenLocal())
jarContainsContractVerifierContracts('fraudDetectionService/build/libs')
BuildResult result = run("check", "--info", "--stacktrace")
result.task(":fraudDetectionService:check").outcome == TaskOutcome.UP_TO_DATE
result.task(":loanApplicationService:check").outcome == TaskOutcome.UP_TO_DATE
}
}

View File

@@ -16,6 +16,8 @@
package org.springframework.cloud.contract.verifier.plugin
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.TaskOutcome
import spock.lang.Ignore
import spock.lang.Stepwise
@@ -35,6 +37,9 @@ class ScenarioProjectSpec extends ContractVerifierIntegrationSpec {
expect:
runTasksSuccessfully(checkAndPublishToMavenLocal())
jarContainsContractVerifierContracts('fraudDetectionService/build/libs')
BuildResult result = run("check", "--info", "--stacktrace")
result.task(":fraudDetectionService:check").outcome == TaskOutcome.UP_TO_DATE
result.task(":loanApplicationService:check").outcome == TaskOutcome.UP_TO_DATE
}
def "should pass basic flow for JUnit"() {
@@ -45,6 +50,26 @@ class ScenarioProjectSpec extends ContractVerifierIntegrationSpec {
emptySourceSet()
runTasksSuccessfully(checkAndPublishToMavenLocal())
jarContainsContractVerifierContracts('fraudDetectionService/build/libs')
BuildResult result = run("check", "--info", "--stacktrace")
result.task(":fraudDetectionService:check").outcome == TaskOutcome.UP_TO_DATE
result.task(":loanApplicationService:check").outcome == TaskOutcome.UP_TO_DATE
}
def "should properly work with build cache"() {
given:
def gradleUserHomeDir = new File(testProjectDir, ".gradleUserHome")
gradleUserHomeDir.mkdirs()
String[] tasks = ["-g ${gradleUserHomeDir}", "clean", "check", "publishToMavenLocal", "--info", "--stacktrace", "--build-cache"]
assert fileExists("build.gradle")
expect:
runTasksSuccessfully(tasks)
jarContainsContractVerifierContracts('fraudDetectionService/build/libs')
BuildResult result = run(tasks)
result.task(":fraudDetectionService:copyContracts").outcome == TaskOutcome.FROM_CACHE
result.task(":fraudDetectionService:generateContractTests").outcome == TaskOutcome.FROM_CACHE
result.task(":fraudDetectionService:contractTest").outcome == TaskOutcome.FROM_CACHE
result.task(":fraudDetectionService:generateClientStubs").outcome == TaskOutcome.FROM_CACHE
result.task(":loanApplicationService:check").outcome == TaskOutcome.UP_TO_DATE
}
}

View File

@@ -97,7 +97,7 @@ configure([project(':fraudDetectionService'), project(':loanApplicationService')
contractsDslDir = file("${project.projectDir.absolutePath}/mappings/")
generatedTestSourcesDir = file("${project.buildDir}/generated-test-sources/")
stubsOutputDir = stubsOutputDirRoot
sourceSet = "test"
sourceSet = "contractTest"
}
jar {
@@ -146,5 +146,5 @@ configure(project(':loanApplicationService')) {
into "src/test/resources/mappings"
}
project.tasks.named("generateContractTests").get().dependsOn('copyCollaboratorStubs')
project.tasks.named("test").get().dependsOn('copyCollaboratorStubs')
}

View File

@@ -139,8 +139,8 @@ configure(listOf(project(":loanApplicationService"))) {
into("src/test/resources/mappings")
}
val generateContractTests by existing
generateContractTests {
val test by existing
test {
dependsOn(copyCollaboratorStubs)
}
}