diff --git a/samples/standalone/dsl/http-client/src/test/java/com/example/loan/GoodbyeWorldTests.java b/samples/standalone/dsl/http-client/src/test/java/com/example/loan/GoodbyeWorldTests.java index 651f5e0e70..93de688655 100644 --- a/samples/standalone/dsl/http-client/src/test/java/com/example/loan/GoodbyeWorldTests.java +++ b/samples/standalone/dsl/http-client/src/test/java/com/example/loan/GoodbyeWorldTests.java @@ -34,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(webEnvironment = WebEnvironment.NONE) @AutoConfigureStubRunner(ids = { "com.example:http-server-dsl"}, - repositoryRoot = "stubs://classpath://contractsAtRuntime/", + repositoryRoot = "stubs://classpath:contractsAtRuntime/", stubsMode = StubRunnerProperties.StubsMode.LOCAL, generateStubs = true) public class GoodbyeWorldTests { diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/Contract.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/Contract.java index 8f7abe57d1..f8ee81b65d 100644 --- a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/Contract.java +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/Contract.java @@ -89,6 +89,12 @@ public class Contract { */ private boolean ignored; + /** + * Whether the contract is in progress. It's not ignored, but the feature is not yet + * finished. Used together with the {@code generateStubs} option. + */ + private boolean inProgress; + public Contract() { } @@ -259,6 +265,17 @@ public class Contract { this.ignored = true; } + /** + * Whether the contract is in progress or not. + */ + public void inProgress() { + this.inProgress = true; + } + + public boolean isInProgress() { + return this.inProgress; + } + public Integer getPriority() { return priority; } @@ -335,6 +352,10 @@ public class Contract { this.ignored = ignored; } + public void setInProgress(boolean inProgress) { + this.inProgress = inProgress; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/FileStubDownloader.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/FileStubDownloader.java index 0b6174ebae..ec99e6b82a 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/FileStubDownloader.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/FileStubDownloader.java @@ -20,7 +20,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; -import java.util.AbstractMap; +import java.nio.file.Paths; import java.util.Collections; import java.util.List; import java.util.Map; @@ -134,14 +134,42 @@ class StubsStubDownloader implements StubDownloader { boolean shouldFindProducer = shouldFindProducer(); if (!shouldFindProducer) { String schemeSpecific = schemeSpecificPart(); - log.info("Stubs are present under [" + schemeSpecific + "]"); - return new AbstractMap.SimpleEntry<>(stubConfiguration, - new File(schemeSpecific)); + log.info("Stubs are present under [" + schemeSpecific + + "]. Will copy them to a temporary directory."); + return new ResourceResolvingStubDownloader(stubRunnerOptions, + this::repoRootForSchemeSpecificPart, this::anyPattern) + .downloadAndUnpackStubJar(stubConfiguration); } return new ResourceResolvingStubDownloader(stubRunnerOptions, this::repoRoot, this::gavPattern).downloadAndUnpackStubJar(stubConfiguration); } + private RepoRoots repoRootForSchemeSpecificPart(StubRunnerOptions stubRunnerOptions, + StubConfiguration configuration) { + String specificPart = schemeSpecificPart(); + specificPart = specificPart.endsWith("/") ? specificPart : (specificPart + "/"); + specificPart = specificPart + "**/*.*"; + return new RepoRoots(Collections.singleton(new RepoRoot(specificPart))); + } + + private Pattern anyPattern(StubConfiguration config) { + return Pattern.compile(resolvePath() + "(.*)"); + } + + private String resolvePath() { + String schemeSpecificPart = schemeSpecificPart(); + Resource resource = ResourceResolver.resource(schemeSpecificPart); + if (resource != null) { + try { + return Paths.get(resource.getURI()).toString(); + } + catch (IOException ex) { + return schemeSpecificPart; + } + } + return schemeSpecificPart; + } + // for group id a.b.c and artifact id d // a.b.c/d // a/b/c/d diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/ResourceResolvingStubDownloader.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/ResourceResolvingStubDownloader.java index f14759e184..392ca3f67c 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/ResourceResolvingStubDownloader.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/ResourceResolvingStubDownloader.java @@ -40,17 +40,12 @@ import shaded.com.google.common.base.Function; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.util.StringUtils; class ResourceResolvingStubDownloader implements StubDownloader { private static final Log log = LogFactory .getLog(ResourceResolvingStubDownloader.class); - private static final int TEMP_DIR_ATTEMPTS = 10000; - - private static final String LATEST_VERSION = "+"; - private final StubRunnerOptions stubRunnerOptions; private final BiFunction repoRootFunction; @@ -71,6 +66,7 @@ class ResourceResolvingStubDownloader implements StubDownloader { @Override public Map.Entry downloadAndUnpackStubJar( StubConfiguration config) { + registerShutdownHook(); List repoRoots = repoRootFunction.apply(stubRunnerOptions, config); List paths = toPaths(repoRoots); List resources = resolveResources(paths); @@ -81,7 +77,7 @@ class ResourceResolvingStubDownloader implements StubDownloader { throw new IllegalStateException("No stubs were found on classpath for [" + config.getGroupId() + ":" + config.getArtifactId() + "]"); } - final File tmp = createTempDir(); + final File tmp = TemporaryFileStorage.createTempDir("classpath-stubs"); if (stubRunnerOptions.isDeleteStubsAfterTest()) { tmp.deleteOnExit(); } @@ -93,7 +89,7 @@ class ResourceResolvingStubDownloader implements StubDownloader { if (log.isDebugEnabled()) { log.debug("Relative path for resource is [" + relativePath + "]"); } - if (StringUtils.isEmpty(relativePath)) { + if (relativePath == null) { log.warn("Unable to match the URI [" + resource.getURI() + "]"); continue; } @@ -117,6 +113,11 @@ class ResourceResolvingStubDownloader implements StubDownloader { tmp); } + private void registerShutdownHook() { + Runtime.getRuntime().addShutdownHook(new Thread(() -> TemporaryFileStorage + .cleanup(stubRunnerOptions.isDeleteStubsAfterTest()))); + } + private void copyTheFoundFiles(File tmp, Resource resource, String relativePath) throws IOException { // the relative path is OS agnostic and contains / only @@ -133,6 +134,7 @@ class ResourceResolvingStubDownloader implements StubDownloader { if (!newFile.exists() && !isDirectory(resource)) { try (InputStream stream = resource.getInputStream()) { Files.copy(stream, newFile.toPath()); + TemporaryFileStorage.add(newFile); } } if (log.isDebugEnabled()) { @@ -157,14 +159,29 @@ class ResourceResolvingStubDownloader implements StubDownloader { String relativePathPicker(Resource resource, Pattern groupAndArtifactPattern) throws IOException { - String uri = resource.getURI().toString(); - Matcher groupAndArtifactMatcher = groupAndArtifactPattern.matcher(uri); - if (groupAndArtifactMatcher.matches()) { + Matcher groupAndArtifactMatcher = matcher(resource, groupAndArtifactPattern); + if (groupAndArtifactMatcher.matches() + && groupAndArtifactMatcher.groupCount() > 2) { MatchResult groupAndArtifactResult = groupAndArtifactMatcher.toMatchResult(); return groupAndArtifactResult.group(2) + groupAndArtifactResult.group(3); } + else if (groupAndArtifactMatcher.matches()) { + return groupAndArtifactMatcher.group(1); + } else { - return ""; + return null; + } + } + + private Matcher matcher(Resource resource, Pattern groupAndArtifactPattern) + throws IOException { + try { + String path = resource.getURI().getPath(); + return groupAndArtifactPattern.matcher(path); + } + catch (Exception ex) { + String path = resource.getURI().toString(); + return groupAndArtifactPattern.matcher(path); } } @@ -192,21 +209,6 @@ class ResourceResolvingStubDownloader implements StubDownloader { return resources; } - // Taken from Guava - private File createTempDir() { - File baseDir = new File(System.getProperty("java.io.tmpdir")); - String baseName = System.currentTimeMillis() + "-"; - for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) { - File tempDir = new File(baseDir, baseName + counter); - if (tempDir.mkdir()) { - return tempDir; - } - } - throw new IllegalStateException("Failed to create directory within " - + TEMP_DIR_ATTEMPTS + " attempts (tried " + baseName + "0 to " + baseName - + (TEMP_DIR_ATTEMPTS - 1) + ")"); - } - } class RepoRoot { diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/TemporaryFileStorage.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/TemporaryFileStorage.java index cbfe01f534..d998e77aa3 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/TemporaryFileStorage.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/TemporaryFileStorage.java @@ -29,8 +29,6 @@ import java.util.concurrent.LinkedBlockingQueue; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import static java.nio.file.Files.createTempDirectory; - /** * Stores all generated temporary folders with stubs. * @@ -41,6 +39,8 @@ final class TemporaryFileStorage { private static final Log log = LogFactory.getLog(TemporaryFileStorage.class); + private static final int TEMP_DIR_ATTEMPTS = 10000; + /** * There are problems with removal of stubs unpacked to a temporary folder. That's why * we're creating a bounded in-memory storage of unpacked files and later we register @@ -104,14 +104,19 @@ final class TemporaryFileStorage { } } + // taken from Guava static File createTempDir(String tempDirPrefix) { - try { - return createTempDirectory(tempDirPrefix).toFile(); - } - catch (IOException e) { - throw new IllegalStateException( - "Cannot create tmp dir with prefix: [" + tempDirPrefix + "]", e); + File baseDir = new File(System.getProperty("java.io.tmpdir")); + String baseName = tempDirPrefix + "-" + System.currentTimeMillis() + "-"; + for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) { + File tempDir = new File(baseDir, baseName + counter); + if (tempDir.mkdir()) { + return tempDir; + } } + throw new IllegalStateException("Failed to create directory within " + + TEMP_DIR_ATTEMPTS + " attempts (tried " + baseName + "0 to " + baseName + + (TEMP_DIR_ATTEMPTS - 1) + ")"); } } diff --git a/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/StubsStubDownloaderTests.java b/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/StubsStubDownloaderTests.java index 00cda7106a..f2dc08151f 100644 --- a/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/StubsStubDownloaderTests.java +++ b/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/StubsStubDownloaderTests.java @@ -39,7 +39,11 @@ public class StubsStubDownloaderTests { .downloadAndUnpackStubJar(new StubConfiguration("lv.spring.cloud:bye")); BDDAssertions.then(entry).isNotNull(); - BDDAssertions.then(entry.getValue().getPath()).endsWith("repository/mappings"); + BDDAssertions.then(entry.getValue()).exists(); + BDDAssertions.then(new File(entry.getValue(), "pl/spring/cloud/bye/pl_bye.json")) + .exists(); + BDDAssertions.then(new File(entry.getValue(), "lv/spring/cloud/bye/lv_bye.json")) + .exists(); } @Test 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 2c5ba80eea..47fa5e6cba 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 @@ -113,7 +113,9 @@ class TestGenerator { void generateTestClasses(final String basePackageName) { ListMultimap contracts = contractFileScanner. findContracts() - contracts.asMap().entrySet().each { + contracts.asMap().entrySet() + .findAll { Map.Entry> entry -> !entry.value.any { it.anyInProgress() }} + .each { Map.Entry> entry -> processIncludedDirectory( relativizeContractPath(entry), (Collection) entry. diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/ContractsToYaml.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/ContractsToYaml.groovy index 029261164f..9b4caa540f 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/ContractsToYaml.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/ContractsToYaml.groovy @@ -58,6 +58,7 @@ class ContractsToYaml { } yamlContract.name = contract.name yamlContract.ignored = contract.ignored + yamlContract.inProgress = contract.inProgress yamlContract.description = contract.description yamlContract.label = contract.label request(contract, yamlContract) diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlContract.java b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlContract.java index 3bb2b75686..6e2f3f22b7 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlContract.java +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlContract.java @@ -52,6 +52,8 @@ public class YamlContract { public boolean ignored; + public boolean inProgress; + public static class Request { public String method; diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlToContracts.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlToContracts.groovy index 5ac695948d..81a20ae0e9 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlToContracts.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/converter/YamlToContracts.groovy @@ -98,6 +98,9 @@ class YamlToContracts { if (yamlContract.ignored) { ignored() } + if (yamlContract.inProgress) { + inProgress() + } if (yamlContract.request?.method) { request { method(yamlContract.request?.method) diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/file/ContractMetadata.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/file/ContractMetadata.groovy index 7315eae8db..25239502f4 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/file/ContractMetadata.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/file/ContractMetadata.groovy @@ -86,6 +86,10 @@ class ContractMetadata { return this.convertedContractWithMetadata .find { it.contract == contract } } + + boolean anyInProgress() { + return this.convertedContract.any { it.inProgress } + } } @CompileStatic diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/converter/YamlContractConverterSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/converter/YamlContractConverterSpec.groovy index 181a8041d4..d948b4ab54 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/converter/YamlContractConverterSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/converter/YamlContractConverterSpec.groovy @@ -716,6 +716,7 @@ label: null name: "post1" priority: null ignored: false +inProgress: false ''' String expectedYaml2 = '''\ --- @@ -757,6 +758,7 @@ label: null name: "post2" priority: null ignored: false +inProgress: false ''' when: Map strings = converter.store([ @@ -985,6 +987,7 @@ ignored: false name("fooo") label("card_rejected") ignored() + inProgress() input { messageFrom("input") messageBody([ @@ -1093,6 +1096,7 @@ ignored: false YamlContract yamlContract = yamlContracts.first() yamlContract.name == "fooo" yamlContract.ignored == true + yamlContract.inProgress == true yamlContract.label == "card_rejected" yamlContract.input.messageFrom == "input" yamlContract.input.messageBody == [