From 909930a6f43394998ffc130cb443236b345fa7dc Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 8 Aug 2019 14:15:24 +0200 Subject: [PATCH] Added failOnInProgress with this change your plugin can fail the build if you have any in progress contracts fixes gh-1156 --- .../asciidoc/_project-features-contract.adoc | 2 + docs/src/main/asciidoc/gradle-project.adoc | 1 + docs/src/main/asciidoc/howto.adoc | 9 +- docs/src/main/asciidoc/maven-project.adoc | 1 + .../plugin/ContractVerifierExtension.groovy | 13 ++ .../plugin/ExtensionToProperties.groovy | 3 +- .../maven/verifier/GenerateTestsMojo.java | 16 ++- .../contract/verifier/TestGenerator.groovy | 19 +++ .../ContractVerifierConfigProperties.java | 16 +++ .../contract/verifier/TestGeneratorTests.java | 126 ++++++++++++++++++ .../builder/SingleTestGeneratorSpec.groovy | 6 + 11 files changed, 207 insertions(+), 5 deletions(-) create mode 100644 spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/TestGeneratorTests.java diff --git a/docs/src/main/asciidoc/_project-features-contract.adoc b/docs/src/main/asciidoc/_project-features-contract.adoc index 888208d444..9ddc5a94dd 100644 --- a/docs/src/main/asciidoc/_project-features-contract.adoc +++ b/docs/src/main/asciidoc/_project-features-contract.adoc @@ -166,6 +166,8 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=in_progre ---- ==== +You can set the value of the `failOnInProgress` Spring Cloud Contract plugin property to ensure that your build will break when at least one contract in progress remains in your sources. + [[contract-dsl-passing-values-from-files]] ==== Passing Values from Files diff --git a/docs/src/main/asciidoc/gradle-project.adoc b/docs/src/main/asciidoc/gradle-project.adoc index 644ff58e66..1854cd170d 100644 --- a/docs/src/main/asciidoc/gradle-project.adoc +++ b/docs/src/main/asciidoc/gradle-project.adoc @@ -309,6 +309,7 @@ JAR is available offline, remotely, and so on). * `deleteStubsAfterTest`: If set to `false`, do not remove any downloaded contracts from temporary directories. * `failOnNoContracts`: When enabled, will throw an exception when no contracts were found. Defaults to `true`. +* `failOnInProgress`: If set to true then if any contracts that are in progress are found, will break the build. On the producer side you need to be explicit about the fact that you have contracts in progress and take into consideration that you might be causing false positive test execution results on the consumer side.. Defaults to `true`. There is also the `contractRepository { ... }` closure that contains the following properties diff --git a/docs/src/main/asciidoc/howto.adoc b/docs/src/main/asciidoc/howto.adoc index 273ff81dfe..945001c37d 100644 --- a/docs/src/main/asciidoc/howto.adoc +++ b/docs/src/main/asciidoc/howto.adoc @@ -1448,4 +1448,11 @@ If you want to generate stubs at runtime for contracts, it's enough to switch th If you want Stub Runner not to fail if no stubs were found, it's enough to switch the `generateStubs` property in the `@AutoConfigureStubRunner` annotation, or call the `withFailOnNoStubs(false)` method on the JUnit Rule or Extension. You can read more about this in <> of the documentation. -If you want the plugins not to fail the build when no contracts were found, you can set the `failOnNoStubs` flag in Maven or call the `contractRepository { failOnNoStubs(false) }` Closure in Gradle. \ No newline at end of file +If you want the plugins not to fail the build when no contracts were found, you can set the `failOnNoStubs` flag in Maven or call the `contractRepository { failOnNoStubs(false) }` Closure in Gradle. + +[[how-to-mark-contract-in-progress]] +== How can I Mark that a Contract Is in Progress + +If a contract is in progress, it means that the on the producer side tests will not be generated, but the stub will be. You can read more about this in <> of the documentation. + +In a CI build, before going to production, you would like to ensure that no in progress contracts are there on the classpath. That's because you may lead to false positives. That's why, by default, in the Spring Cloud Contract plugin, we set the value of `failOnInProgress` to `true`. If you want to allow such contracts when tests are to be generated, just set the flag to `false`. \ No newline at end of file diff --git a/docs/src/main/asciidoc/maven-project.adoc b/docs/src/main/asciidoc/maven-project.adoc index 62662ffa38..e19f65b1be 100644 --- a/docs/src/main/asciidoc/maven-project.adoc +++ b/docs/src/main/asciidoc/maven-project.adoc @@ -225,6 +225,7 @@ extends `com.example.base.BaseClass`. This setting takes precedence over * `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. * `failOnNoContracts`: When enabled, will throw an exception when no contracts were found. Defaults to `true`. +* `failOnInProgress`: If set to true then if any contracts that are in progress are found, will break the build. On the producer side you need to be explicit about the fact that you have contracts in progress and take into consideration that you might be causing false positive test execution results on the consumer side.. Defaults to `true`. If you want to download your contract definitions from a Maven repository, you can use the following options: diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy index 208b310941..0bd6f3cf95 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy @@ -145,6 +145,14 @@ class ContractVerifierExtension { */ boolean failOnNoContracts = true + /** + * If set to true then if any contracts that are in progress are found, will break the + * build. On the producer side you need to be explicit about the fact that you have + * contracts in progress and take into consideration that you might be causing false + * positive test execution results on the consumer side. + */ + boolean failOnInProgress = true; + ContractRepository contractRepository = new ContractRepository() /** @@ -252,6 +260,10 @@ class ContractVerifierExtension { this.failOnNoContracts = failOnNoContracts } + void failOnInProgress(boolean failOnInProgress) { + this.failOnInProgress = failOnInProgress + } + ContractVerifierExtension copy() { return new ContractVerifierExtension( testFramework: this.testFramework, @@ -272,6 +284,7 @@ class ContractVerifierExtension { stubsSuffix: this.stubsSuffix, assertJsonSize: this.assertJsonSize, failOnNoContracts: this.failOnNoContracts, + failOnInProgress: this.failOnInProgress, contractRepository: new ContractRepository( repositoryUrl: this.contractRepository.repositoryUrl, username: this.contractRepository.username, diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ExtensionToProperties.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ExtensionToProperties.groovy index 71099d0156..c6dd3dc560 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ExtensionToProperties.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ExtensionToProperties.groovy @@ -33,7 +33,8 @@ class ExtensionToProperties { assertJsonSize: extension.getAssertJsonSize(), packageWithBaseClasses: extension.getPackageWithBaseClasses(), baseClassMappings: extension.getBaseClassMappings(), - excludeBuildFolders: extension.getExcludeBuildFolders() + excludeBuildFolders: extension.getExcludeBuildFolders(), + failOnInProgress: extension.getFailOnInProgress() ) } } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/GenerateTestsMojo.java b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/GenerateTestsMojo.java index 50a3f6c0fc..37d12eba1a 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/GenerateTestsMojo.java +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/main/java/org/springframework/cloud/contract/maven/verifier/GenerateTestsMojo.java @@ -233,8 +233,17 @@ public class GenerateTestsMojo extends AbstractMojo { * When enabled, this flag will tell stub runner to throw an exception when no stubs / * contracts were found. */ - @Parameter(property = "failOnNoStubs", defaultValue = "true") - private boolean failOnNoStubs; + @Parameter(property = "failOnNoContracts", defaultValue = "true") + private boolean failOnNoContracts; + + /** + * If set to true then if any contracts that are in progress are found, will break the + * build. On the producer side you need to be explicit about the fact that you have + * contracts in progress and take into consideration that you might be causing false + * positive test execution results on the consumer side. + */ + @Parameter(property = "failOnInProgress", defaultValue = "true") + private boolean failOnInProgress = true; @Override public void execute() throws MojoExecutionException, MojoFailureException { @@ -259,13 +268,14 @@ public class GenerateTestsMojo extends AbstractMojo { getLog().info( "Generating server tests source code for Spring Cloud Contract Verifier contract verification"); final ContractVerifierConfigProperties config = new ContractVerifierConfigProperties(); + config.setFailOnInProgress(this.failOnInProgress); // download contracts, unzip them and pass as output directory File contractsDirectory = new MavenContractsDownloader(this.project, this.contractDependency, this.contractsPath, this.contractsRepositoryUrl, this.contractsMode, getLog(), this.contractsRepositoryUsername, this.contractsRepositoryPassword, this.contractsRepositoryProxyHost, this.contractsRepositoryProxyPort, this.deleteStubsAfterTest, - this.contractsProperties, this.failOnNoStubs) + this.contractsProperties, this.failOnNoContracts) .downloadAndUnpackContractsIfRequired(config, this.contractsDirectory); getLog().info( diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/TestGenerator.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/TestGenerator.groovy index 47fa5e6cba..0ef22f88d1 100755 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/TestGenerator.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/TestGenerator.groovy @@ -89,6 +89,16 @@ class TestGenerator { .build() } + protected TestGenerator(ContractVerifierConfigProperties configProperties, SingleTestGenerator generator, FileSaver saver, ContractFileScanner contractFileScanner) { + this.configProperties = configProperties + if (configProperties.contractsDslDir == null) { + throw new ContractVerifierException("Stubs directory not found under " + configProperties.contractsDslDir) + } + this.generator = generator + this.saver = saver + this.contractFileScanner = contractFileScanner + } + int generate() { generateTestClasses(basePackageName()) NamesUtil.recrusiveDirectoryToPackage(configProperties.generatedTestSourcesDir) @@ -113,6 +123,15 @@ class TestGenerator { void generateTestClasses(final String basePackageName) { ListMultimap contracts = contractFileScanner. findContracts() + Set>> inProgress = contracts.asMap().entrySet() + .findAll { Map.Entry> entry -> entry.value.any { it.anyInProgress() }} + if (!inProgress.isEmpty() && configProperties.failOnInProgress) { + throw new IllegalStateException("In progress contracts found in paths [" + inProgress.collect { it.key.toString() }.join(",") + "] and the switch [failOnInProgress] is set to [true]. Either unmark those contracts as in progress, or set the switch to [false].") + } + processAllNotInProgress(contracts,basePackageName) + } + + @PackageScope Set>> processAllNotInProgress(ListMultimap contracts, String basePackageName) { contracts.asMap().entrySet() .findAll { Map.Entry> entry -> !entry.value.any { it.anyInProgress() }} .each { diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/config/ContractVerifierConfigProperties.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/config/ContractVerifierConfigProperties.java index 3693d49a62..6d36151f44 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/config/ContractVerifierConfigProperties.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/config/ContractVerifierConfigProperties.java @@ -175,6 +175,14 @@ public class ContractVerifierConfigProperties { */ private boolean excludeBuildFolders; + /** + * If set to true then if any contracts that are in progress are found, will break the + * build. On the producer side you need to be explicit about the fact that you have + * contracts in progress and take into consideration that you might be causing false + * positive test execution results on the consumer side. + */ + private boolean failOnInProgress = true; + @Deprecated public void setTargetFramework(TestFramework targetFramework) { log.warn("Please use the [testFramework] field. [targetFramework] is deprecated"); @@ -366,4 +374,12 @@ public class ContractVerifierConfigProperties { this.excludeBuildFolders = excludeBuildFolders; } + public boolean isFailOnInProgress() { + return this.failOnInProgress; + } + + public void setFailOnInProgress(boolean failOnInProgress) { + this.failOnInProgress = failOnInProgress; + } + } diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/TestGeneratorTests.java b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/TestGeneratorTests.java new file mode 100644 index 0000000000..a0c0ac09b8 --- /dev/null +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/TestGeneratorTests.java @@ -0,0 +1,126 @@ +/* + * Copyright 2013-2019 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; + +import java.io.File; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.assertj.core.api.BDDAssertions; +import org.junit.Test; +import org.mockito.BDDMockito; +import wiremock.com.google.common.collect.ArrayListMultimap; +import wiremock.com.google.common.collect.ListMultimap; + +import org.springframework.cloud.contract.spec.Contract; +import org.springframework.cloud.contract.verifier.builder.SingleTestGenerator; +import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties; +import org.springframework.cloud.contract.verifier.file.ContractFileScanner; +import org.springframework.cloud.contract.verifier.file.ContractMetadata; + +public class TestGeneratorTests { + + @Test + public void should_throw_exception_when_in_progress_contracts_found() { + // given: + ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties(); + properties.setFailOnInProgress(true); + properties.setContractsDslDir(new File(".")); + SingleTestGenerator singleTestGenerator = BDDMockito + .mock(SingleTestGenerator.class); + FileSaver fileSaver = BDDMockito.mock(FileSaver.class); + // and: + ArrayListMultimap multimap = ArrayListMultimap.create(); + Path path = new File(".").toPath(); + multimap.put(path, + new ContractMetadata(path, false, 0, null, Contract.make(it -> { + it.inProgress(); + it.request(r -> { + r.method(r.GET()); + r.url("/foo"); + }); + it.response(r -> { + r.status(r.OK()); + }); + }))); + ContractFileScanner scanner = new ContractFileScanner(null, null, null) { + @Override + public ListMultimap findContracts() { + return multimap; + } + }; + // and: + TestGenerator testGenerator = new TestGenerator(properties, singleTestGenerator, + fileSaver, scanner); + + // then: + BDDAssertions.thenThrownBy(() -> { + // when: + testGenerator.generateTestClasses("com.example"); + }).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("In progress contracts found in"); + } + + @Test + public void should_not_throw_exception_when_in_progress_contracts_found_but_the_fail_on_in_progress_switch_is_off() { + // given: + ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties(); + properties.setFailOnInProgress(false); + properties.setContractsDslDir(new File(".")); + SingleTestGenerator singleTestGenerator = BDDMockito + .mock(SingleTestGenerator.class); + FileSaver fileSaver = BDDMockito.mock(FileSaver.class); + // and: + ArrayListMultimap multimap = ArrayListMultimap.create(); + Path path = new File(".").toPath(); + multimap.put(path, + new ContractMetadata(path, false, 0, null, Contract.make(it -> { + it.inProgress(); + it.request(r -> { + r.method(r.GET()); + r.url("/foo"); + }); + it.response(r -> { + r.status(r.OK()); + }); + }))); + ContractFileScanner scanner = new ContractFileScanner(null, null, null) { + @Override + public ListMultimap findContracts() { + return multimap; + } + }; + // and: + TestGenerator testGenerator = new TestGenerator(properties, singleTestGenerator, + fileSaver, scanner) { + @Override + Set>> processAllNotInProgress( + ListMultimap contracts, + String basePackageName) { + return null; + } + }; + + // when: + testGenerator.generateTestClasses("com.example"); + + // then: noExceptionThrown() + } + +} diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGeneratorSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGeneratorSpec.groovy index 01f6c0b2e9..910ca8144d 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGeneratorSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGeneratorSpec.groovy @@ -16,14 +16,20 @@ package org.springframework.cloud.contract.verifier.builder +import java.nio.file.Paths + import org.junit.Rule import org.junit.rules.TemporaryFolder import spock.lang.Issue import spock.lang.Specification +import wiremock.com.google.common.collect.ArrayListMultimap +import org.springframework.cloud.contract.spec.Contract +import org.springframework.cloud.contract.verifier.FileSaver 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.file.ContractFileScanner import org.springframework.cloud.contract.verifier.file.ContractMetadata import org.springframework.cloud.contract.verifier.util.SyntaxChecker import org.springframework.util.FileSystemUtils