Added the option "inProgress" to the contract

without this change we can't have a contract that will generate a stub but not a test. Of course that's for a reason since it's overriding the very essence of contract test.
with this change we assume that the users know what they're doing and that they will use this flag rarerly. Cause it means that you can have a false positive.

related to gh-881
This commit is contained in:
Marcin Grzejszczak
2019-08-06 15:39:32 +02:00
parent 4ae133a0f8
commit 2be3d0ab86
12 changed files with 117 additions and 41 deletions

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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

View File

@@ -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<StubRunnerOptions, StubConfiguration, RepoRoots> repoRootFunction;
@@ -71,6 +66,7 @@ class ResourceResolvingStubDownloader implements StubDownloader {
@Override
public Map.Entry<StubConfiguration, File> downloadAndUnpackStubJar(
StubConfiguration config) {
registerShutdownHook();
List<RepoRoot> repoRoots = repoRootFunction.apply(stubRunnerOptions, config);
List<String> paths = toPaths(repoRoots);
List<Resource> 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 {

View File

@@ -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) + ")");
}
}

View File

@@ -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

View File

@@ -113,7 +113,9 @@ class TestGenerator {
void generateTestClasses(final String basePackageName) {
ListMultimap<Path, ContractMetadata> contracts = contractFileScanner.
findContracts()
contracts.asMap().entrySet().each {
contracts.asMap().entrySet()
.findAll { Map.Entry<Path, Collection<ContractMetadata>> entry -> !entry.value.any { it.anyInProgress() }}
.each {
Map.Entry<Path, Collection<ContractMetadata>> entry ->
processIncludedDirectory(
relativizeContractPath(entry), (Collection<ContractMetadata>) entry.

View File

@@ -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)

View File

@@ -52,6 +52,8 @@ public class YamlContract {
public boolean ignored;
public boolean inProgress;
public static class Request {
public String method;

View File

@@ -98,6 +98,9 @@ class YamlToContracts {
if (yamlContract.ignored) {
ignored()
}
if (yamlContract.inProgress) {
inProgress()
}
if (yamlContract.request?.method) {
request {
method(yamlContract.request?.method)

View File

@@ -86,6 +86,10 @@ class ContractMetadata {
return this.convertedContractWithMetadata
.find { it.contract == contract }
}
boolean anyInProgress() {
return this.convertedContract.any { it.inProgress }
}
}
@CompileStatic

View File

@@ -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<String, byte[]> 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 == [