diff --git a/README.adoc b/README.adoc index ff772e6a54..4412bca9da 100644 --- a/README.adoc +++ b/README.adoc @@ -287,7 +287,7 @@ run the following command: ./scripts/parallelBuild.sh ``` -To use eight 8 cores, run thke following command: +To use eight 8 cores, run the following command: ``` CORES=8 ./scripts/parallelBuild.sh diff --git a/docker/spring-cloud-contract-docker/project/gradle/wrapper/gradle-wrapper.jar b/docker/spring-cloud-contract-docker/project/gradle/wrapper/gradle-wrapper.jar index 490fda8577..62d4c05355 100644 Binary files a/docker/spring-cloud-contract-docker/project/gradle/wrapper/gradle-wrapper.jar and b/docker/spring-cloud-contract-docker/project/gradle/wrapper/gradle-wrapper.jar differ diff --git a/docker/spring-cloud-contract-docker/project/gradlew b/docker/spring-cloud-contract-docker/project/gradlew index 2fe81a7d95..fbd7c51583 100755 --- a/docker/spring-cloud-contract-docker/project/gradlew +++ b/docker/spring-cloud-contract-docker/project/gradlew @@ -82,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -129,6 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath diff --git a/docker/spring-cloud-contract-docker/project/gradlew.bat b/docker/spring-cloud-contract-docker/project/gradlew.bat index 9109989e3c..a9f778a7a9 100644 --- a/docker/spring-cloud-contract-docker/project/gradlew.bat +++ b/docker/spring-cloud-contract-docker/project/gradlew.bat @@ -84,6 +84,7 @@ set CMD_LINE_ARGS=%* set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/ContractDownloader.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/ContractDownloader.java index 579bbd4b97..98c8749861 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/ContractDownloader.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/ContractDownloader.java @@ -193,9 +193,8 @@ public class ContractDownloader { } private String wrapWithAntPattern(String path) { - String changedPath = path.replace(File.separator, "/"); - return "**" + surroundWithSeparator(changedPath).replace(File.separator, "/") - + "**/"; + String changedPath = surroundWithSeparator(path).replace(File.separator, "/"); + return "**" + changedPath.replace(File.separator, "/") + "**/"; } private String groupArtifactToPattern(File contractsDirectory) { 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 43f8258b90..a42ad885a1 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,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; -import java.nio.file.Paths; import java.util.Collections; import java.util.List; import java.util.Map; @@ -76,7 +75,27 @@ public class FileStubDownloader implements StubDownloaderBuilder { if (StringUtils.isEmpty(location) || !isProtocolAccepted(location)) { return null; } - return new StubsResource(location); + // Can be resolving a resource for Classpath as fallback + if (!location.startsWith("stubs://file://")) { + return new StubsResource(location); + } + // Convert any windows file format path to a uri + String correctlyFormattedLocation = convertLocationToUriFormat(location); + return new StubsResource(correctlyFormattedLocation); + } + + private String convertLocationToUriFormat(String location) { + final String correctlyFormattedLocation = separatorsToUnix(location); + final String rawPath = correctlyFormattedLocation.replace("stubs://file://", ""); + if (rawPath.charAt(0) != '/') { + return "stubs://file:///" + rawPath; + } + return correctlyFormattedLocation; + } + + private String separatorsToUnix(String location) { + return location != null && location.indexOf(92) != -1 + ? location.replace('\\', '/') : location; } } @@ -161,7 +180,7 @@ class StubsStubDownloader implements StubDownloader { Resource resource = ResourceResolver.resource(schemeSpecificPart); if (resource != null) { try { - return Paths.get(resource.getURI()).toString(); + return resource.getURL().getFile(); } catch (IOException ex) { return schemeSpecificPart; diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/GitRepo.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/GitRepo.java index 5859660cd3..3ad1496eb4 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/GitRepo.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/GitRepo.java @@ -55,10 +55,10 @@ import org.eclipse.jgit.transport.SshTransport; import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.eclipse.jgit.util.FS; -import org.eclipse.jgit.util.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.util.FileSystemUtils; import org.springframework.util.ResourceUtils; /** @@ -221,7 +221,7 @@ class GitRepo { return git; } catch (GitAPIException | URISyntaxException e) { - deleteBaseDirIfExists(); + deleteBaseDirIfExists("Failed to initialize base directory"); throw new IllegalStateException(e); } } @@ -236,7 +236,7 @@ class GitRepo { return command.call(); } catch (GitAPIException e) { - deleteBaseDirIfExists(); + deleteBaseDirIfExists("Failed to delete base directory"); throw e; } finally { @@ -290,13 +290,10 @@ class GitRepo { return false; } - private void deleteBaseDirIfExists() { + private void deleteBaseDirIfExists(String errorMessage) { if (this.basedir.exists()) { - try { - FileUtils.delete(this.basedir, FileUtils.RECURSIVE); - } - catch (IOException e) { - throw new IllegalStateException("Failed to initialize base directory", e); + if (!FileSystemUtils.deleteRecursively(this.basedir)) { + throw new IllegalStateException(errorMessage); } } } diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/AetherStubDownloaderSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/AetherStubDownloaderSpec.groovy index b048ca5fcf..f153eadd4a 100644 --- a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/AetherStubDownloaderSpec.groovy +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/AetherStubDownloaderSpec.groovy @@ -59,7 +59,7 @@ class AetherStubDownloaderSpec extends Specification { and: String localRepo = AetherFactories.localRepositoryDirectory(true) new File(localRepo, "org/springframework/cloud/spring-cloud-contract-spec" - .replaceAll("/", File.separator)).list().size() > 0 + .replace("/", File.separator)).list().size() > 0 and: AetherStubDownloader aetherStubDownloader = new AetherStubDownloader(stubRunnerOptions) diff --git a/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/FileStubDownloaderTests.java b/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/FileStubDownloaderTests.java new file mode 100644 index 0000000000..2281d2b730 --- /dev/null +++ b/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/FileStubDownloaderTests.java @@ -0,0 +1,46 @@ +/* + * 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.stubrunner; + +import org.assertj.core.api.Assertions; +import org.junit.Test; + +import org.springframework.core.io.Resource; + +/** + * @author Matty A + */ +public class FileStubDownloaderTests { + + @Test + public void resolve() { + final FileStubDownloader fileStubDownloader = new FileStubDownloader(); + Resource expectedUnixResource = new StubsResource("stubs://file:///User/A/B/C"); + Resource expectedWindowsResource = new StubsResource( + "stubs://file:///C:/Users/A/B/C"); + String unixFileFormat = "stubs://file:///User/A/B/C"; + String windowsFileFormat = "stubs://file://C:\\Users\\A\\B\\C"; + String windowsFileFormatCorrectPathStart = "stubs://file:///C:\\Users\\A\\B\\C"; + Assertions.assertThat(expectedUnixResource) + .isEqualTo(fileStubDownloader.resolve(unixFileFormat, null)); + Assertions.assertThat(expectedWindowsResource) + .isEqualTo(fileStubDownloader.resolve(windowsFileFormat, null)); + Assertions.assertThat(expectedWindowsResource).isEqualTo( + fileStubDownloader.resolve(windowsFileFormatCorrectPathStart, null)); + } + +} diff --git a/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/GitStubDownloaderTests.java b/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/GitStubDownloaderTests.java index 3ff11da5f8..3171f82d57 100644 --- a/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/GitStubDownloaderTests.java +++ b/spring-cloud-contract-stub-runner/src/test/java/org/springframework/cloud/contract/stubrunner/GitStubDownloaderTests.java @@ -86,12 +86,12 @@ public class GitStubDownloaderTests { public void should_pick_stubs_for_group_and_artifact_with_version_from_a_git_repo() throws Exception { StubDownloaderBuilder stubDownloaderBuilder = new ScmStubDownloaderBuilder(); + String contractFolderLocation = (file("/git_samples/contract-git/") + .getAbsolutePath() + "/").replace(File.separator, "/"); StubDownloader stubDownloader = stubDownloaderBuilder .build(new StubRunnerOptionsBuilder() .withStubsMode(StubRunnerProperties.StubsMode.REMOTE) - .withStubRepositoryRoot("git://" - + file("/git_samples/contract-git/").getAbsolutePath() - + "/") + .withStubRepositoryRoot("git://" + contractFolderLocation) .withProperties(props()).build()); Map.Entry entry = stubDownloader @@ -107,12 +107,12 @@ public class GitStubDownloaderTests { public void should_pick_latest_build_snapshot_stubs_when_latest_version_set() throws URISyntaxException { StubDownloaderBuilder stubDownloaderBuilder = new ScmStubDownloaderBuilder(); + String contractFolderLocation = (file("/git_samples/contract-git/") + .getAbsolutePath() + "/").replace(File.separator, "/"); StubDownloader stubDownloader = stubDownloaderBuilder .build(new StubRunnerOptionsBuilder() .withStubsMode(StubRunnerProperties.StubsMode.REMOTE) - .withStubRepositoryRoot("git://" - + file("/git_samples/contract-git/").getAbsolutePath() - + "/") + .withStubRepositoryRoot("git://" + contractFolderLocation) .withProperties(props()).build()); Map.Entry entry = stubDownloader @@ -149,12 +149,12 @@ public class GitStubDownloaderTests { public void should_pick_latest_release_stubs_when_release_version_set() throws URISyntaxException { StubDownloaderBuilder stubDownloaderBuilder = new ScmStubDownloaderBuilder(); + String contractFolderLocation = (file("/git_samples/contract-git/") + .getAbsolutePath() + "/").replace(File.separator, "/"); StubDownloader stubDownloader = stubDownloaderBuilder .build(new StubRunnerOptionsBuilder() .withStubsMode(StubRunnerProperties.StubsMode.REMOTE) - .withStubRepositoryRoot("git://" - + file("/git_samples/contract-git/").getAbsolutePath() - + "/") + .withStubRepositoryRoot("git://" + contractFolderLocation) .withProperties(props()).build()); Map.Entry entry = stubDownloader @@ -177,13 +177,14 @@ public class GitStubDownloaderTests { public void should_pick_latest_build_snapshot_stubs_when_latest_version_set_and_latest_folder_exists() throws URISyntaxException { StubDownloaderBuilder stubDownloaderBuilder = new ScmStubDownloaderBuilder(); + String contractFolderLocation = (file( + "/git_samples/contract-predefined-names-git/").getAbsolutePath() + .replace("/", File.separator) + + "/").replace(File.separator, "/"); StubDownloader stubDownloader = stubDownloaderBuilder .build(new StubRunnerOptionsBuilder() .withStubsMode(StubRunnerProperties.StubsMode.REMOTE) - .withStubRepositoryRoot("git://" - + file("/git_samples/contract-predefined-names-git/") - .getAbsolutePath() - + "/") + .withStubRepositoryRoot("git://" + contractFolderLocation) .withProperties(props()).build()); Map.Entry entry = stubDownloader @@ -213,13 +214,13 @@ public class GitStubDownloaderTests { public void should_pick_release_folder_when_release_version_set() throws URISyntaxException { StubDownloaderBuilder stubDownloaderBuilder = new ScmStubDownloaderBuilder(); + String contractFolderLocation = (file( + "/git_samples/contract-predefined-names-git/").getAbsolutePath() + "/") + .replace(File.separator, "/"); StubDownloader stubDownloader = stubDownloaderBuilder .build(new StubRunnerOptionsBuilder() .withStubsMode(StubRunnerProperties.StubsMode.REMOTE) - .withStubRepositoryRoot("git://" - + file("/git_samples/contract-predefined-names-git/") - .getAbsolutePath() - + "/") + .withStubRepositoryRoot("git://" + contractFolderLocation) .withProperties(props()).build()); Map.Entry entry = stubDownloader @@ -242,12 +243,12 @@ public class GitStubDownloaderTests { public void should_fail_to_fetch_stubs_when_concrete_version_was_not_specified() throws URISyntaxException { StubDownloaderBuilder stubDownloaderBuilder = new ScmStubDownloaderBuilder(); + String contractFolderLocation = (file("/git_samples/contract-git/") + .getAbsolutePath() + "/").replace(File.separator, "/"); StubDownloader stubDownloader = stubDownloaderBuilder .build(new StubRunnerOptionsBuilder() .withStubsMode(StubRunnerProperties.StubsMode.REMOTE) - .withStubRepositoryRoot("git://" - + file("/git_samples/contract-git").getAbsolutePath() - + "/") + .withStubRepositoryRoot("git://" + contractFolderLocation) .withProperties(props()).build()); try { diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlToXPathsConverter.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlToXPathsConverter.groovy index d5bcf94772..6d2d42ff05 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlToXPathsConverter.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlToXPathsConverter.groovy @@ -16,7 +16,9 @@ package org.springframework.cloud.contract.verifier.util.xml +import com.sun.org.apache.xml.internal.security.utils.DOMNamespaceContext +import javax.xml.namespace.NamespaceContext import java.util.stream.IntStream import javax.xml.parsers.DocumentBuilder @@ -87,10 +89,13 @@ class XmlToXPathsConverter { private static String getNodeValue(String path, Object body) { XPath xPath = XPathFactory.newInstance().newXPath() - DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance() + DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance() + builderFactory.setNamespaceAware(true) + DocumentBuilder documentBuilder = builderFactory .newDocumentBuilder() Document parsedXml = documentBuilder. parse(new InputSource(new StringReader(body as String))) + xPath.setNamespaceContext(new DOMNamespaceContext(parsedXml.documentElement)) return xPath.evaluate(path, parsedXml.documentElement) } diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlToXPathsConverterSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlToXPathsConverterSpec.groovy new file mode 100644 index 0000000000..f3878b9642 --- /dev/null +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlToXPathsConverterSpec.groovy @@ -0,0 +1,40 @@ +package org.springframework.cloud.contract.verifier.util.xml + +import spock.lang.Shared +import spock.lang.Specification +import spock.lang.Unroll + +import javax.xml.xpath.XPathExpressionException + +class XmlToXPathsConverterSpec extends Specification { + @Shared + String namedXml = ''' + customer@test.com + + ''' + @Shared + String unnamedXml = ''' + customer@test.com + + ''' + + @Unroll + def "should generate [#expectedValue] for xPath [#value]"() { + expect: + value == expectedValue + where: + value || expectedValue + XmlToXPathsConverter.retrieveValueFromBody("/ns1:customer/email/text()", namedXml) || '''customer@test.com''' + XmlToXPathsConverter.retrieveValueFromBody("/customer/email/text()", namedXml) || '''''' + XmlToXPathsConverter.retrieveValueFromBody("/customer/email/text()", unnamedXml) || '''customer@test.com''' + } + + @Unroll + def "should throw exception when searching for inexistent name space"() { + when: + XmlToXPathsConverter.retrieveValueFromBody("/ns1:customer/email/text()", unnamedXml) + then: + def e = thrown(XPathExpressionException) + e.message.contains('Prefix must resolve to a namespace: ns1') + } +}