Add a switch to fail on no stubs (#1153)

* Add a switch to fail on no stubs

without this change we throw an exception if no stubs / contracts were found
with this change we catch exceptions from aether stub downloader and if there are no stubs found whatsover, and a switch is set to fail in that case, then we do throw an exception that no stubs / contracts were found

fixes gh-895
This commit is contained in:
Marcin Grzejszczak
2019-08-06 11:54:52 +02:00
committed by GitHub
parent 4f652ad1e6
commit 4ae133a0f8
20 changed files with 182 additions and 41 deletions

View File

@@ -29,7 +29,6 @@ import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.Proxy;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
@@ -205,14 +204,6 @@ public class AetherStubDownloader implements StubDownloader {
}
}
private boolean resolvedFromLocalRepo(ArtifactResult result) {
return result.getRepository() instanceof LocalRepository;
}
private boolean shouldDownloadFromRemote() {
return !remoteReposMissing() && !this.workOffline;
}
private String getVersion(String stubsGroup, String stubsModule, String version,
String classifier) {
if (StringUtils.isEmpty(version) || LATEST_VERSION_IN_IVY.equals(version)) {
@@ -228,20 +219,26 @@ public class AetherStubDownloader implements StubDownloader {
@Override
public Map.Entry<StubConfiguration, File> downloadAndUnpackStubJar(
StubConfiguration stubConfiguration) {
String version = getVersion(stubConfiguration.groupId,
stubConfiguration.artifactId, stubConfiguration.version,
stubConfiguration.classifier);
if (log.isDebugEnabled()) {
log.debug("Will download the stub for version [" + version + "]");
try {
String version = getVersion(stubConfiguration.groupId,
stubConfiguration.artifactId, stubConfiguration.version,
stubConfiguration.classifier);
if (log.isDebugEnabled()) {
log.debug("Will download the stub for version [" + version + "]");
}
File unpackedJar = unpackedJar(version, stubConfiguration.groupId,
stubConfiguration.artifactId, stubConfiguration.classifier);
if (unpackedJar == null) {
return null;
}
return new AbstractMap.SimpleEntry<>(new StubConfiguration(
stubConfiguration.groupId, stubConfiguration.artifactId, version,
stubConfiguration.classifier), unpackedJar);
}
File unpackedJar = unpackedJar(version, stubConfiguration.groupId,
stubConfiguration.artifactId, stubConfiguration.classifier);
if (unpackedJar == null) {
catch (Exception ex) {
log.warn("Exception occurred while trying to fetch the stubs", ex);
return null;
}
return new AbstractMap.SimpleEntry<>(new StubConfiguration(
stubConfiguration.groupId, stubConfiguration.artifactId, version,
stubConfiguration.classifier), unpackedJar);
}
private String resolveHighestArtifactVersion(String stubsGroup, String stubsModule,

View File

@@ -67,6 +67,21 @@ class CompositeStubDownloader implements StubDownloader {
@Override
public Map.Entry<StubConfiguration, File> downloadAndUnpackStubJar(
StubConfiguration stubConfiguration) {
Map.Entry<StubConfiguration, File> entry = entry(stubConfiguration);
if (entry != null) {
return entry;
}
log.warn("No matching stubs or contracts were found");
if (this.stubRunnerOptions.isFailOnNoStubs()) {
throw new IllegalArgumentException("No stubs or contracts were found for ["
+ stubConfiguration.toColonSeparatedDependencyNotation()
+ "] and the switch to fail on no stubs was set.");
}
return null;
}
private Map.Entry<StubConfiguration, File> entry(
StubConfiguration stubConfiguration) {
for (StubDownloaderBuilder builder : this.builders) {
StubDownloader downloader = builder.build(this.stubRunnerOptions);
if (downloader == null) {

View File

@@ -122,6 +122,12 @@ public class StubRunnerOptions {
*/
private boolean generateStubs;
/**
* When enabled, this flag will tell stub runner to throw an exception when no stubs /
* contracts were found.
*/
private boolean failOnNoStubs = true;
/**
* Map of properties that can be passed to custom
* {@link org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder}.
@@ -134,7 +140,7 @@ public class StubRunnerOptions {
Map<StubConfiguration, Integer> stubIdsToPortMapping, String username,
String password, final StubRunnerProxyOptions stubRunnerProxyOptions,
boolean stubsPerConsumer, String consumerName, String mappingsOutputFolder,
boolean deleteStubsAfterTest, boolean generateStubs,
boolean deleteStubsAfterTest, boolean generateStubs, boolean failOnNoStubs,
Map<String, String> properties,
Class<? extends HttpServerStubConfigurer> httpServerStubConfigurer) {
this.minPortValue = minPortValue;
@@ -153,6 +159,7 @@ public class StubRunnerOptions {
this.mappingsOutputFolder = mappingsOutputFolder;
this.deleteStubsAfterTest = deleteStubsAfterTest;
this.generateStubs = generateStubs;
this.failOnNoStubs = failOnNoStubs;
this.properties = properties;
this.httpServerStubConfigurer = httpServerStubConfigurer;
}
@@ -179,6 +186,8 @@ public class StubRunnerOptions {
System.getProperty("stubrunner.delete-stubs-after-test", "true")))
.withGenerateStubs(Boolean.parseBoolean(
System.getProperty("stubrunner.generate-stubs", "false")))
.withFailOnNoStubs(Boolean.parseBoolean(
System.getProperty("stubrunner.fail-on-no-stubs", "false")))
.withProperties(stubRunnerProps());
builder = httpStubConfigurer(builder);
String proxyHost = System.getProperty("stubrunner.proxy.host");
@@ -332,6 +341,10 @@ public class StubRunnerOptions {
return this.generateStubs;
}
public boolean isFailOnNoStubs() {
return this.failOnNoStubs;
}
public Map<String, String> getProperties() {
return this.properties;
}

View File

@@ -71,6 +71,8 @@ public class StubRunnerOptionsBuilder {
private boolean generateStubs;
private boolean failOnNoStubs = true;
private Map<String, String> properties = new HashMap<>();
private Class httpServerStubConfigurer = HttpServerStubConfigurer.NoOpHttpServerStubConfigurer.class;
@@ -197,6 +199,7 @@ public class StubRunnerOptionsBuilder {
? options.stubIdsToPortMapping : new LinkedHashMap<>();
this.deleteStubsAfterTest = options.isDeleteStubsAfterTest();
this.generateStubs = options.isGenerateStubs();
this.failOnNoStubs = options.isFailOnNoStubs();
this.properties = options.getProperties();
this.httpServerStubConfigurer = options.getHttpServerStubConfigurer();
return this;
@@ -219,6 +222,11 @@ public class StubRunnerOptionsBuilder {
return this;
}
public StubRunnerOptionsBuilder withFailOnNoStubs(boolean failOnNoStubs) {
this.failOnNoStubs = failOnNoStubs;
return this;
}
public StubRunnerOptionsBuilder withProperties(Map<String, String> properties) {
this.properties = properties;
return this;
@@ -236,7 +244,8 @@ public class StubRunnerOptionsBuilder {
buildDependencies(), this.stubIdsToPortMapping, this.username,
this.password, this.stubRunnerProxyOptions, this.stubsPerConsumer,
this.consumerName, this.mappingsOutputFolder, this.deleteStubsAfterTest,
this.generateStubs, this.properties, this.httpServerStubConfigurer);
this.generateStubs, this.failOnNoStubs, this.properties,
this.httpServerStubConfigurer);
}
private Collection<StubConfiguration> buildDependencies() {

View File

@@ -258,6 +258,12 @@ public class StubRunnerExtension implements BeforeAllCallback, AfterAllCallback,
return new PortStubRunnerExtension(this.delegate);
}
@Override
public StubRunnerExtension failOnNoStubs(boolean failOnNoStubs) {
builder().withFailOnNoStubs(failOnNoStubs);
return new PortStubRunnerExtension(this.delegate);
}
@Override
public StubRunnerExtension withProperties(Map<String, String> properties) {
builder().withProperties(properties);

View File

@@ -156,6 +156,13 @@ interface StubRunnerExtensionOptions {
*/
StubRunnerExtension withGenerateStubs(boolean generateStubs);
/**
* @param failOnNoStubs when enabled, this flag will tell stub runner to throw an
* exception when no stubs / contracts were found.
* @return the rule
*/
StubRunnerExtension failOnNoStubs(boolean failOnNoStubs);
/**
* @param properties Map of properties that can be passed to custom
* {@link org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder}

View File

@@ -192,10 +192,16 @@ public class StubRunnerRule implements TestRule, StubFinder, StubRunnerRuleOptio
@Override
public StubRunnerRule withGenerateStubs(boolean generateStubs) {
builder().withGenerateStubs(true);
builder().withGenerateStubs(generateStubs);
return this.delegate;
}
@Override
public StubRunnerRule failOnNoStubs(boolean failOnNoStubs) {
builder().withFailOnNoStubs(failOnNoStubs);
return null;
}
@Override
public StubRunnerRule withProperties(Map<String, String> properties) {
builder().withProperties(properties);

View File

@@ -152,6 +152,13 @@ interface StubRunnerRuleOptions {
*/
StubRunnerRule withGenerateStubs(boolean generateStubs);
/**
* @param failOnNoStubs when enabled, this flag will tell stub runner to throw an
* exception when no stubs / contracts were found.
* @return the rule
*/
StubRunnerRule failOnNoStubs(boolean failOnNoStubs);
/**
* @param properties Map of properties that can be passed to custom
* {@link org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder}

View File

@@ -144,6 +144,12 @@ public @interface AutoConfigureStubRunner {
*/
boolean generateStubs() default false;
/**
* @return when enabled, this flag will tell stub runner to throw an exception when no
* stubs / contracts were found.
*/
boolean failOnNoStubs() default true;
/**
* Configuration for an HTTP server stub.
* @return class that allows to perform additional HTTP server stub configuration

View File

@@ -114,6 +114,12 @@ public class StubRunnerProperties {
*/
private boolean generateStubs;
/**
* When enabled, this flag will tell stub runner to throw an exception when no stubs /
* contracts were found.
*/
private boolean failOnNoStubs = true;
/**
* Map of properties that can be passed to custom
* {@link org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder}.
@@ -260,6 +266,14 @@ public class StubRunnerProperties {
this.generateStubs = generateStubs;
}
public boolean isFailOnNoStubs() {
return this.failOnNoStubs;
}
public void setFailOnNoStubs(boolean failOnNoStubs) {
this.failOnNoStubs = failOnNoStubs;
}
public Class getHttpServerStubConfigurer() {
return this.httpServerStubConfigurer;
}

View File

@@ -43,11 +43,10 @@ class AetherStubDownloaderSpec extends Specification {
AetherStubDownloader aetherStubDownloader = new AetherStubDownloader(stubRunnerOptions)
when:
aetherStubDownloader.downloadAndUnpackStubJar(new StubConfiguration("non.existing.group", "missing-artifact-id", "1.0-SNAPSHOT"))
def entry = aetherStubDownloader.downloadAndUnpackStubJar(new StubConfiguration("non.existing.group", "missing-artifact-id", "1.0-SNAPSHOT"))
then:
IllegalStateException e = thrown(IllegalStateException)
e.message.contains("Exception occurred while trying to download a stub for group")
entry == null
}
def 'should throw an exception when local m2 gets replaced with a temp dir and a jar is not found in remote'() {
@@ -65,11 +64,10 @@ class AetherStubDownloaderSpec extends Specification {
AetherStubDownloader aetherStubDownloader = new AetherStubDownloader(stubRunnerOptions)
when:
aetherStubDownloader.downloadAndUnpackStubJar(new StubConfiguration("org.springframework.cloud", "spring-cloud-contract-spec", "+", ""))
def entry = aetherStubDownloader.downloadAndUnpackStubJar(new StubConfiguration("org.springframework.cloud", "spring-cloud-contract-spec", "+", ""))
then:
IllegalArgumentException e = thrown(IllegalArgumentException)
e.message.contains("Could not find metadata org.springframework.cloud:spring-cloud-contract-spec/maven-metadata.xml in remote0")
entry == null
}
@RestoreSystemProperties

View File

@@ -231,7 +231,7 @@ class StubRunnerOptionsBuilderSpec extends Specification {
given:
StubRunnerOptionsBuilder builder = builder.withOptions(new StubRunnerOptions(1, 2, new FileSystemResource("root"), StubRunnerProperties.StubsMode.LOCAL,
"classifier", [new StubConfiguration("a:b:c")], [(new StubConfiguration("a:b:c")): 3], "foo", "bar",
new StubRunnerOptions.StubRunnerProxyOptions("host", 4), true, "consumer", "folder", false, true, [foo: "bar"], Foo))
new StubRunnerOptions.StubRunnerProxyOptions("host", 4), true, "consumer", "folder", false, true, false, [foo: "bar"], Foo))
builder.withStubs("foo:bar:baz")
when:
StubRunnerOptions options = builder.build()
@@ -252,6 +252,7 @@ class StubRunnerOptionsBuilderSpec extends Specification {
options.mappingsOutputFolder == "folder"
options.deleteStubsAfterTest == false
options.generateStubs == true
options.failOnNoStubs == false
options.properties == [foo: "bar"]
options.httpServerStubConfigurer == Foo
}
@@ -261,7 +262,7 @@ class StubRunnerOptionsBuilderSpec extends Specification {
StubRunnerOptionsBuilder builder = builder.withOptions(new StubRunnerOptions(1, 2, new FileSystemResource("root"),
StubRunnerProperties.StubsMode.CLASSPATH, "classifier",
[new StubConfiguration("a:b:c")], [(new StubConfiguration("a:b:c")): 3], "username123", "password123",
new StubRunnerOptions.StubRunnerProxyOptions("host", 4), true, "consumer", "folder", false, true, [:], Foo))
new StubRunnerOptions.StubRunnerProxyOptions("host", 4), true, "consumer", "folder", false, true, true, [:], Foo))
builder.withStubs("foo:bar:baz")
when:
String options = builder.build().toString()
@@ -293,6 +294,7 @@ class StubRunnerOptionsBuilderSpec extends Specification {
System.setProperty("stubrunner.properties.bar.bar", "foo")
System.setProperty("stubrunner.delete-stubs-after-test", "false")
System.setProperty("stubrunner.generate-stubs", "true")
System.setProperty("stubrunner.fail-on-no-stubs", "false")
System.setProperty("stubrunner.http-server-stub-configurer", "org.springframework.cloud.contract.stubrunner.Foo")
when:
StubRunnerOptions options = StubRunnerOptions.fromSystemProps()
@@ -310,6 +312,7 @@ class StubRunnerOptionsBuilderSpec extends Specification {
options.stubsPerConsumer == true
options.deleteStubsAfterTest == false
options.generateStubs == true
options.failOnNoStubs == false
options.consumerName == "consumer"
options.mappingsOutputFolder == "folder"
options.properties == ["foo-bar": "bar", "foo-baz": "baz", "bar.bar": "foo"]

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.contract.stubrunner;
import java.io.File;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -57,6 +58,33 @@ public class CompositeStubDownloaderBuilderTests {
BDDAssertions.then(downloader).isNull();
}
@Test
public void should_return_null_when_no_entries_were_found() {
EmptyStubDownloaderBuilder emptyStubDownloaderBuilder = new EmptyStubDownloaderBuilder();
CompositeStubDownloaderBuilder builder = new CompositeStubDownloaderBuilder(
Collections.singletonList(emptyStubDownloaderBuilder));
StubDownloader downloader = builder
.build(new StubRunnerOptionsBuilder().withFailOnNoStubs(false).build());
Map.Entry<StubConfiguration, File> entry = downloader
.downloadAndUnpackStubJar(new StubConfiguration("a:b:v"));
BDDAssertions.then(entry).isNull();
}
@Test
public void should_throw_exception_when_no_entries_were_found_and_a_switch_to_throw_exception_was_set() {
EmptyStubDownloaderBuilder emptyStubDownloaderBuilder = new EmptyStubDownloaderBuilder();
CompositeStubDownloaderBuilder builder = new CompositeStubDownloaderBuilder(
Collections.singletonList(emptyStubDownloaderBuilder));
StubDownloader downloader = builder
.build(new StubRunnerOptionsBuilder().withFailOnNoStubs(true).build());
BDDAssertions.thenThrownBy(
() -> downloader.downloadAndUnpackStubJar(new StubConfiguration("a:b:v")))
.isInstanceOf(IllegalArgumentException.class);
}
}
class EmptyStubDownloaderBuilder implements StubDownloaderBuilder {

View File

@@ -50,7 +50,8 @@ public class StubDownloaderBuilderProviderTests {
.singletonList(StubDownloaderBuilderProviderTests.this.two);
}
};
StubRunnerOptions options = new StubRunnerOptionsBuilder().build();
StubRunnerOptions options = new StubRunnerOptionsBuilder()
.withFailOnNoStubs(false).build();
provider.get(options, this.three)
.downloadAndUnpackStubJar(new StubConfiguration("a:b:c"));

View File

@@ -267,7 +267,8 @@ class ContractVerifierExtension {
password: this.contractRepository.password,
proxyPort: this.contractRepository.proxyPort,
proxyHost: this.contractRepository.proxyHost,
cacheDownloadedContracts: this.contractRepository.cacheDownloadedContracts
cacheDownloadedContracts: this.contractRepository.cacheDownloadedContracts,
failOnNoStubs: this.contractRepository.failOnNoStubs
),
contractDependency: new Dependency(
groupId: this.contractDependency.groupId,
@@ -366,6 +367,12 @@ class ContractVerifierExtension {
*/
boolean cacheDownloadedContracts = true
/**
* When enabled, this flag will tell stub runner to throw an exception when no stubs /
* contracts were found.
*/
boolean failOnNoStubs = true
void repositoryUrl(String repositoryUrl) {
this.repositoryUrl = repositoryUrl
}
@@ -389,5 +396,9 @@ class ContractVerifierExtension {
void cacheDownloadedContracts(boolean cacheDownloadedContracts) {
this.cacheDownloadedContracts = cacheDownloadedContracts
}
void failOnNoStubs(boolean failOnNoStubs) {
this.failOnNoStubs = failOnNoStubs
}
}
}

View File

@@ -91,6 +91,7 @@ class GradleContractsDownloader {
if (extension.contractRepository.proxyPort) {
options = options.withProxy(extension.contractRepository.proxyHost, extension.contractRepository.proxyPort)
}
options = options.withFailOnNoStubs(extension.contractRepository.failOnNoStubs)
return options.build()
}

View File

@@ -181,6 +181,13 @@ public class ConvertMojo extends AbstractMojo {
@Component(role = MavenResourcesFiltering.class, hint = "default")
private MavenResourcesFiltering mavenResourcesFiltering;
/**
* 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;
@Override
public void execute() throws MojoExecutionException {
if (this.skip) {
@@ -271,8 +278,9 @@ public class ConvertMojo extends AbstractMojo {
getLog(), this.contractsRepositoryUsername,
this.contractsRepositoryPassword, this.contractsRepositoryProxyHost,
this.contractsRepositoryProxyPort, this.deleteStubsAfterTest,
this.contractsProperties).downloadAndUnpackContractsIfRequired(config,
this.contractsDirectory);
this.contractsProperties, this.failOnNoStubs)
.downloadAndUnpackContractsIfRequired(config,
this.contractsDirectory);
}
private File stubsOutputDir(String rootPath) {

View File

@@ -229,6 +229,13 @@ public class GenerateTestsMojo extends AbstractMojo {
@Parameter(property = "contractsProperties")
private Map<String, String> contractsProperties = new HashMap<>();
/**
* 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;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (this.skip || this.mavenTestSkip || this.skipTests) {
@@ -258,8 +265,9 @@ public class GenerateTestsMojo extends AbstractMojo {
this.contractsMode, getLog(), this.contractsRepositoryUsername,
this.contractsRepositoryPassword, this.contractsRepositoryProxyHost,
this.contractsRepositoryProxyPort, this.deleteStubsAfterTest,
this.contractsProperties).downloadAndUnpackContractsIfRequired(config,
this.contractsDirectory);
this.contractsProperties, this.failOnNoStubs)
.downloadAndUnpackContractsIfRequired(config,
this.contractsDirectory);
getLog().info(
"Directory with contract is present at [" + contractsDirectory + "]");
setupConfig(config, contractsDirectory);

View File

@@ -71,12 +71,14 @@ class MavenContractsDownloader {
private final Map<String, String> contractsProperties;
private final boolean failOnNoStubs;
MavenContractsDownloader(MavenProject project, Dependency contractDependency,
String contractsPath, String contractsRepositoryUrl,
StubRunnerProperties.StubsMode stubsMode, Log log, String repositoryUsername,
String repositoryPassword, String repositoryProxyHost,
Integer repositoryProxyPort, boolean deleteStubsAfterTest,
Map<String, String> contractsProperties) {
Map<String, String> contractsProperties, boolean failOnNoStubs) {
this.project = project;
this.contractDependency = contractDependency;
this.contractsPath = contractsPath;
@@ -90,6 +92,7 @@ class MavenContractsDownloader {
this.stubDownloaderBuilderProvider = new StubDownloaderBuilderProvider();
this.deleteStubsAfterTest = deleteStubsAfterTest;
this.contractsProperties = contractsProperties;
this.failOnNoStubs = failOnNoStubs;
}
File downloadAndUnpackContractsIfRequired(ContractVerifierConfigProperties config,
@@ -144,7 +147,8 @@ class MavenContractsDownloader {
.withStubsMode(this.stubsMode).withUsername(this.repositoryUsername)
.withPassword(this.repositoryPassword)
.withDeleteStubsAfterTest(this.deleteStubsAfterTest)
.withProperties(this.contractsProperties);
.withProperties(this.contractsProperties)
.withFailOnNoStubs(this.failOnNoStubs);
if (StringUtils.hasText(this.contractsRepositoryUrl)) {
builder.withStubRepositoryRoot(this.contractsRepositoryUrl);
}

View File

@@ -49,8 +49,7 @@ public class FailFastLoanApplicationServiceTests {
assertThat(throwable.getCause()).isInstanceOf(BeanInstantiationException.class);
assertThat(throwable.getCause().getCause())
.isInstanceOf(IllegalArgumentException.class).hasMessageContaining(
"For groupId [org.springframework.cloud.contract.verifier.stubs] artifactId [should-not-be-found] "
+ "and classifier [stubs] the version was not resolved! The following exceptions took place");
"No stubs or contracts were found for [org.springframework.cloud.contract.verifier.stubs:should-not-be-found:+:stubs] and the switch to fail on no stubs was set.");
}
@Test