From 7b2cce751370cc950921ae2fbb5d259873ccd5a9 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 18 Dec 2018 13:20:39 +0100 Subject: [PATCH] Fixed issues with bound ports --- docs/src/main/asciidoc/verifier_faq.adoc | 38 +-------- .../contract/stubrunner/HttpServerStub.java | 18 ++-- ...ubRunnerWireMockTestExecutionListener.java | 66 +++----------- .../wiremock/WireMockHttpServerStub.java | 70 ++++++++++----- .../WireMockHttpServerStubSpec.groovy | 4 +- .../DslToWireMockClientConverterSpec.groovy | 2 +- .../org/springframework/cloud/MvcTest.java | 2 +- .../verifier/builder/JavaTestGenerator.groovy | 4 +- .../builder/SingleTestGeneratorSpec.groovy | 9 +- .../wiremock/WireMockConfiguration.java | 52 ++++++++---- .../WireMockTestExecutionListener.java | 85 +++++++++---------- .../contract/wiremock/WireMockUtils.java | 65 -------------- ...AutoConfigureWireMockApplicationTests.java | 24 +++--- ...gureWireMockAutoStubsApplicationTests.java | 10 ++- ...reMockRandomPortHttpsApplicationTests.java | 17 ++-- ...iremockServerRestDocsApplicationTests.java | 4 +- ...ServerRestDocsMatcherApplicationTests.java | 4 +- 17 files changed, 190 insertions(+), 284 deletions(-) delete mode 100644 spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockUtils.java diff --git a/docs/src/main/asciidoc/verifier_faq.adoc b/docs/src/main/asciidoc/verifier_faq.adoc index 13d24a3bc5..10a5ccc3ae 100644 --- a/docs/src/main/asciidoc/verifier_faq.adoc +++ b/docs/src/main/asciidoc/verifier_faq.adoc @@ -936,40 +936,4 @@ started will be attached. Yes! With version 1.2.0 we've added such a possibility. It's enough to call `file(...)` method in the DSL and provide a path relative to where the contract lays. -If you're using YAML just use the `bodyFromFile` property. - -==== Why do I sometimes get `SocketException` - -When using an HTTP client (e.g. `RestTemplate`) and running several tests that share a Spring context, you might sometimes get the following exception: - -``` -java.net.SocketException: Unexpected end of file from server -``` - -Tom Akehurst, the creator of WireMock did the following analysis of this issue. - -> I looked at tcpdump while running the failing test. `HttpUrlConnection` is doing something weird - it's creating a connection in a previous test case, which works fine, then the usual `fin` -> `fin ack` etc. ending handshake happens. But it seems it isn't discarded, but reused after that. Because the server thinks (rightly) that the connection is closed, it just sends a RST packet. Calling the `/__admin` endpoint just happened to remove the dead connection from the pool. This also fixes the problem (which using the Java HTTP client): System.setProperty("http.keepAlive", "false"); - -There are the ways to solve this problem. - -First, just use a different HTTP client for `RestTemplate`. Example for using Apache HTTP client: - -.pom.xml -[source,xml,indent=0] ----- -include::{standalone_restdocs_path}/http-client/pom.xml[tags=httpclient,indent=0] ----- - -[source,java,indent=0] ----- -include::{standalone_restdocs_path}/http-client/src/main/java/com/example/loan/LoanApplicationService.java[tags=custom_request_factory,indent=0] ----- - -Second option is to set the system property. You can set it either in code or pass it to your tests via a plugin. - -[source,java,indent=0] ----- -static { - System.setProperty("http.keepAlive", "false"); -} ----- \ No newline at end of file +If you're using YAML just use the `bodyFromFile` property. \ No newline at end of file diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/HttpServerStub.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/HttpServerStub.java index f45ba6ad78..8bc2c34479 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/HttpServerStub.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/HttpServerStub.java @@ -10,6 +10,7 @@ import java.util.Collection; * @since 1.1.0 */ public interface HttpServerStub { + /** * Port on which the server is running */ @@ -21,14 +22,12 @@ public interface HttpServerStub { boolean isRunning(); /** - * Starts the server on a random port. Should return itself - * to allow chaining. + * Starts the server on a random port. Should return itself to allow chaining. */ HttpServerStub start(); /** - * Starts the server on a given port. Should return itself - * to allow chaining. + * Starts the server on a given port. Should return itself to allow chaining. */ HttpServerStub start(int port); @@ -38,8 +37,15 @@ public interface HttpServerStub { HttpServerStub stop(); /** - * Registers the stub files in the HTTP server stub. Should return itself - * to allow chaining. + * Resets the server. Should return itself to allow chaining. + */ + default HttpServerStub reset() { + return this; + } + + /** + * Registers the stub files in the HTTP server stub. Should return itself to allow + * chaining. */ HttpServerStub registerMappings(Collection stubFiles); diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/StubRunnerWireMockTestExecutionListener.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/StubRunnerWireMockTestExecutionListener.java index 8b777c471d..e78b726a49 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/StubRunnerWireMockTestExecutionListener.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/StubRunnerWireMockTestExecutionListener.java @@ -17,23 +17,16 @@ package org.springframework.cloud.contract.stubrunner.provider.wiremock; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import com.github.tomakehurst.wiremock.stubbing.StubMapping; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.contract.stubrunner.HttpServerStub; import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner; -import org.springframework.cloud.contract.wiremock.WireMockUtils; -import org.springframework.context.ApplicationContext; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestContext; import org.springframework.test.context.support.AbstractTestExecutionListener; /** - * Stops the {@link HttpServerStub} after each test class + * Marks context to be restarted if at least one stub has a fixed port * * @author Marcin Grzejszczak * @since 1.2.6 @@ -44,55 +37,24 @@ public final class StubRunnerWireMockTestExecutionListener private static final Log log = LogFactory .getLog(StubRunnerWireMockTestExecutionListener.class); - private static Map> STUBS = new ConcurrentHashMap<>(); - - @Override - public void beforeTestClass(TestContext testContext) { - if (testContext.getTestClass().getAnnotationsByType(AutoConfigureStubRunner.class).length == 0) { - if (log.isTraceEnabled()) { - log.trace("No @AutoConfigureStubRunner annotation found on [" + testContext.getTestClass() + "]. Skipping"); - } - return; - } - Map stubs = STUBS - .get(testContext.getApplicationContext()); - if (stubs != null) { - if (log.isDebugEnabled()) { - log.debug("Found a matching application context from [" - + testContext.getTestClass().getName() + "]"); - } - for (Map.Entry entry : stubs - .entrySet()) { - while (entry.getKey().isRunning()) { - entry.getKey().stop(); - } - List mappings = entry.getValue().mappings; - if (log.isDebugEnabled()) { - log.debug("Stopped a running WireMock instance at " + "port [" - + entry.getValue().port + "] with stub mappings size [" - + mappings.size() + "]. Restarting the stub."); - } - entry.getKey().start(entry.getValue().port); - entry.getKey().registerDescriptors(mappings); - WireMockUtils.getMappingsEndpoint(entry.getValue().port); - } - } - } - @Override public void afterTestClass(TestContext testContext) { if (testContext.getTestClass().getAnnotationsByType(AutoConfigureStubRunner.class).length == 0) { - if (log.isTraceEnabled()) { - log.trace("No @AutoConfigureStubRunner annotation found on [" + testContext.getTestClass() + "]. Skipping"); + if (log.isDebugEnabled()) { + log.debug("No @AutoConfigureStubRunner annotation found on [" + testContext.getTestClass() + "]. Skipping"); } return; } - STUBS.put(testContext.getApplicationContext(), WireMockHttpServerStub.SERVERS); - if (log.isDebugEnabled()) { - log.debug("Stopping servers " + WireMockHttpServerStub.SERVERS); - } - for (HttpServerStub serverStub : WireMockHttpServerStub.SERVERS.keySet()) { - serverStub.stop(); + if (WireMockHttpServerStub.SERVERS.values().stream().anyMatch(p -> !p.random)) { + if (log.isWarnEnabled()) { + log.warn("You've used fixed ports for WireMock setup - " + + "will mark context as dirty. Please use random ports, as much " + + "as possible. Your tests will be faster and more reliable and this" + + "warning will go away"); + } + testContext.markApplicationContextDirty(DirtiesContext.HierarchyMode.EXHAUSTIVE); } + // potential race condition + WireMockHttpServerStub.SERVERS.clear(); } } \ No newline at end of file diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStub.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStub.java index 747e34b503..fbee55382d 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStub.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStub.java @@ -68,9 +68,9 @@ public class WireMockHttpServerStub implements HttpServerStub { private WireMockServer wireMockServer; private WireMockConfiguration config() { - if (ClassUtils.isPresent("org.springframework.cloud.contract.wiremock.WireMockSpring", null)) { - return WireMockSpring.options() - .extensions(responseTransformers()); + if (ClassUtils.isPresent( + "org.springframework.cloud.contract.wiremock.WireMockSpring", null)) { + return WireMockSpring.options().extensions(responseTransformers()); } return new WireMockConfiguration().extensions(responseTransformers()); } @@ -83,7 +83,8 @@ public class WireMockHttpServerStub implements HttpServerStub { for (WireMockExtensions wireMockExtension : wireMockExtensions) { extensions.addAll(wireMockExtension.extensions()); } - } else { + } + else { extensions.add(new DefaultResponseTransformer(false, helpers())); } return extensions.toArray(new Extension[extensions.size()]); @@ -91,9 +92,8 @@ public class WireMockHttpServerStub implements HttpServerStub { /** * Override this if you want to register your own helpers - * - * @deprecated - please use the {@link WireMockExtensions} mechanism and pass - * the helpers in your implementation + * @deprecated - please use the {@link WireMockExtensions} mechanism and pass the + * helpers in your implementation */ @Deprecated protected Map helpers() { @@ -121,23 +121,34 @@ public class WireMockHttpServerStub implements HttpServerStub { } return this; } - return start(SocketUtils.findAvailableTcpPort()); + int port = SocketUtils.findAvailableTcpPort(); + HttpServerStub serverStub = start(port); + cacheStubServer(true, port); + return serverStub; } @Override public HttpServerStub start(int port) { - this.wireMockServer = new WireMockServer(config().port(port) - .notifier(new Slf4jNotifier(true))); + this.wireMockServer = new WireMockServer( + config().port(port).notifier(new Slf4jNotifier(true))); this.wireMockServer.start(); if (log.isDebugEnabled()) { log.debug("Started WireMock at port [" + port + "]"); } - if (!SERVERS.containsKey(this)) { - SERVERS.put(this, new PortAndMappings(port, new ArrayList<>())); - } + cacheStubServer(false, port); return this; } + @Override + public HttpServerStub reset() { + this.wireMockServer.resetAll(); + return this; + } + + private void cacheStubServer(boolean random, int port) { + SERVERS.put(this, new PortAndMappings(random, port, new ArrayList<>())); + } + @Override public HttpServerStub stop() { if (!isRunning()) { @@ -159,7 +170,8 @@ public class WireMockHttpServerStub implements HttpServerStub { return this; } - @Override public String registeredMappings() { + @Override + public String registeredMappings() { Collection mappings = new ArrayList<>(); for (StubMapping stubMapping : this.wireMockServer.getStubMappings()) { mappings.add(stubMapping.toString()); @@ -207,17 +219,19 @@ public class WireMockHttpServerStub implements HttpServerStub { try { stubMappings.add(registerDescriptor(wireMock, mappingDescriptor)); if (log.isDebugEnabled()) { - log.debug("Registered stub mappings from [" + mappingDescriptor + "]"); + log.debug( + "Registered stub mappings from [" + mappingDescriptor + "]"); } } catch (Exception e) { if (log.isDebugEnabled()) { - log.debug("Failed to register the stub mapping [" + mappingDescriptor + "]", e); + log.debug("Failed to register the stub mapping [" + mappingDescriptor + + "]", e); } } } PortAndMappings portAndMappings = SERVERS.get(this); - SERVERS.put(this, new PortAndMappings(portAndMappings.port, stubMappings)); + SERVERS.put(this, new PortAndMappings(portAndMappings.random, portAndMappings.port, stubMappings)); } private StubMapping registerDescriptor(WireMock wireMock, File mappingDescriptor) { @@ -228,7 +242,8 @@ public class WireMockHttpServerStub implements HttpServerStub { void registerDescriptors(List stubMappings) { if (log.isDebugEnabled()) { - log.debug("Registering stub mappings size [" + stubMappings.size() + "] at port [" + port() + "]"); + log.debug("Registering stub mappings size [" + stubMappings.size() + + "] at port [" + port() + "]"); } for (StubMapping mapping : stubMappings) { wireMock().register(mapping); @@ -240,21 +255,30 @@ public class WireMockHttpServerStub implements HttpServerStub { } private void registerHealthCheck(WireMock wireMock, String url, String body) { - wireMock.register( - WireMock.get(WireMock.urlEqualTo(url)).willReturn(WireMock.aResponse().withBody(body).withStatus(200))); + wireMock.register(WireMock.get(WireMock.urlEqualTo(url)) + .willReturn(WireMock.aResponse().withBody(body).withStatus(200))); } + } class PortAndMappings { + + final boolean random; final Integer port; final List mappings; - PortAndMappings(Integer port, List mappings) { + PortAndMappings(boolean random, Integer port, List mappings) { + this.random = random; this.port = port; this.mappings = mappings; } - @Override public String toString() { - return "PortAndMappings{" + "port=" + this.port + ", mappings=" + this.mappings.size() + '}'; + @Override + public String toString() { + return "PortAndMappings{" + + "random=" + this.random + + ", port=" + this.port + + ", mappings=" + this.mappings + + '}'; } } \ No newline at end of file diff --git a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStubSpec.groovy b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStubSpec.groovy index 64008e1091..d6c75838d4 100644 --- a/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStubSpec.groovy +++ b/spring-cloud-contract-stub-runner/src/test/groovy/org/springframework/cloud/contract/stubrunner/provider/wiremock/WireMockHttpServerStubSpec.groovy @@ -51,7 +51,7 @@ class WireMockHttpServerStubSpec extends Specification { expect: "surprise!" == new RestTemplate().getForObject("http://localhost:" + mappingDescriptor.port() + "/ping", String.class) cleanup: - mappingDescriptor.stop() + mappingDescriptor?.stop() } def 'should make WireMock print out logs on INFO'() { @@ -69,6 +69,6 @@ class WireMockHttpServerStubSpec extends Specification { capture.toString().contains("Matched response definition") cleanup: - mappingDescriptor.stop() + mappingDescriptor?.stop() } } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy index cd15a12b23..434020aa3b 100755 --- a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy @@ -42,7 +42,7 @@ import org.springframework.util.SocketUtils class DslToWireMockClientConverterSpec extends Specification { - static int port = SocketUtils.findAvailableTcpPort() + int port = SocketUtils.findAvailableTcpPort() @Rule public WireMockRule wireMockRule = new WireMockRule(port) @Rule public TemporaryFolder tmpFolder = new TemporaryFolder() TestRestTemplate restTemplate = new TestRestTemplate() diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/fraudDetectionService/src/test/java/org/springframework/cloud/MvcTest.java b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/fraudDetectionService/src/test/java/org/springframework/cloud/MvcTest.java index 815d5fae43..68b93b961f 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/fraudDetectionService/src/test/java/org/springframework/cloud/MvcTest.java +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/fraudDetectionService/src/test/java/org/springframework/cloud/MvcTest.java @@ -25,7 +25,7 @@ public abstract class MvcTest { @BeforeClass public static void setupTest() throws Exception { - int port = findAvailableTcpPort(8000); + int port = findAvailableTcpPort(10000); URI baseUri = UriBuilder.fromUri("http://localhost").port(port).build(); // Create Server Server server = new Server(port); diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JavaTestGenerator.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JavaTestGenerator.groovy index c3de1c34a7..376f6f67d3 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JavaTestGenerator.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/JavaTestGenerator.groovy @@ -183,13 +183,13 @@ class JavaTestGenerator implements SingleTestGenerator { class ClassPresenceChecker { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()) + private static final Log log = LogFactory.getLog(ClassPresenceChecker) boolean isClassPresent(String className) { try { Class.forName(className) return true - } catch (ClassNotFoundException e) { + } catch (ClassNotFoundException ex) { if (log.isDebugEnabled()) { log.debug("[${className}] is not present on classpath. Will not add a static import.") } 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 08d2873006..fdba43ea8a 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,18 +16,17 @@ package org.springframework.cloud.contract.verifier.builder - import org.junit.Rule import org.junit.rules.TemporaryFolder +import spock.lang.Issue +import spock.lang.Specification + import org.springframework.cloud.contract.verifier.TestGenerator import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties import org.springframework.cloud.contract.verifier.config.TestMode import org.springframework.cloud.contract.verifier.file.ContractMetadata import org.springframework.cloud.contract.verifier.util.SyntaxChecker -import org.springframework.util.FileSystemUtils import org.springframework.util.StringUtils -import spock.lang.Issue -import spock.lang.Specification import static org.springframework.cloud.contract.verifier.config.TestFramework.JUNIT import static org.springframework.cloud.contract.verifier.config.TestFramework.SPOCK @@ -184,7 +183,7 @@ class SingleTestGeneratorSpec extends Specification { SPOCK | TestMode.EXPLICIT | GROOVY_ASSERTER | "ContractsSpec.groovy" } - def "should build test class for #testFramework with Rest Assured 2.x"() { + def "should build test class for #testFramework with Rest Assured 2x"() { given: ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties() properties.targetFramework = testFramework diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java index 8ac5ea3a3e..37beff5bc6 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockConfiguration.java @@ -77,7 +77,7 @@ public class WireMockConfiguration implements SmartLifecycle { private DefaultListableBeanFactory beanFactory; @Autowired - private WireMockProperties wireMock; + WireMockProperties wireMock; @Autowired private ResourceLoader resourceLoader; @@ -100,6 +100,11 @@ public class WireMockConfiguration implements SmartLifecycle { } } if (this.server == null) { + if (log.isDebugEnabled()) { + log.debug("Creating a new server at " + + "http port [" + this.wireMock.getServer().getPort() + "] and " + + "https port [" + this.wireMock.getServer().getHttpsPort() + "]"); + } this.server = new WireMockServer(this.options); } registerStubs(); @@ -126,21 +131,23 @@ public class WireMockConfiguration implements SmartLifecycle { } for (Resource resource : resolver.getResources(pattern)) { this.server.addStubMapping(WireMockStubMapping - .buildFrom(StreamUtils.copyToString(resource.getInputStream(), Charset.forName("UTF-8")))); + .buildFrom(StreamUtils.copyToString(resource.getInputStream(), + Charset.forName("UTF-8")))); } } } } - int port() { - return this.server.port(); - } - void reset() { + if (log.isDebugEnabled()) { + log.debug("Resetting stubs"); + } this.server.resetAll(); } - private void registerFiles(com.github.tomakehurst.wiremock.core.WireMockConfiguration factory) throws IOException { + private void registerFiles( + com.github.tomakehurst.wiremock.core.WireMockConfiguration factory) + throws IOException { List resources = new ArrayList<>(); for (String files : this.wireMock.getServer().getFiles()) { if (StringUtils.hasText(files)) { @@ -154,31 +161,46 @@ public class WireMockConfiguration implements SmartLifecycle { } } if (!resources.isEmpty()) { - ResourcesFileSource fileSource = new ResourcesFileSource(resources.toArray(new Resource[0])); + ResourcesFileSource fileSource = new ResourcesFileSource( + resources.toArray(new Resource[0])); factory.fileSource(fileSource); } } @Override public void start() { + if (isRunning()) { + if (log.isDebugEnabled()) { + log.debug("Server is already running"); + } + return; + } this.server.start(); - WireMock.configureFor("localhost", this.server.port()); + updateCurrentServer(); + } + + private void updateCurrentServer() { + WireMock.configureFor(new WireMock(this.server)); this.running = true; if (log.isDebugEnabled()) { - log.debug("Started WireMock at port [" + this.server.port() + "]. It has [" + this.server.getStubMappings().size() + "] mappings registered"); + log.debug("Started WireMock at port [" + this.server.port() + "]. It has [" + + this.server.getStubMappings().size() + "] mappings registered"); } - WireMockUtils.getMappingsEndpoint(this.port()); } @Override public void stop() { if (this.running) { - reset(); - this.server.shutdownServer(); + this.server.stop(); + this.server = null; this.running = false; + this.options = null; if (log.isDebugEnabled()) { log.debug("Stopped WireMock instance"); } + this.beanFactory.destroySingleton(WIREMOCK_SERVER_BEAN_NAME); + } else if (log.isDebugEnabled()) { + log.debug("Server already stopped"); } } @@ -202,7 +224,6 @@ public class WireMockConfiguration implements SmartLifecycle { stop(); callback.run(); } - } @ConfigurationProperties("wiremock") @@ -269,6 +290,7 @@ class WireMockProperties { public void setFiles(String[] files) { this.files = files; } + } -} \ No newline at end of file +} diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockTestExecutionListener.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockTestExecutionListener.java index fcddd23755..0efaed7f9b 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockTestExecutionListener.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockTestExecutionListener.java @@ -20,11 +20,12 @@ package org.springframework.cloud.contract.wiremock; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestContext; import org.springframework.test.context.support.AbstractTestExecutionListener; /** - * Stops the WireMock server after each test class and restarts it before every class + * Dirties the test context if WireMock was running on a fixed port * * @author Marcin Grzejszczak * @since 1.2.6 @@ -33,59 +34,51 @@ public final class WireMockTestExecutionListener extends AbstractTestExecutionLi private static final Log log = LogFactory.getLog(WireMockTestExecutionListener.class); - @Override public void beforeTestClass(TestContext testContext) { - try { - if (wireMockConfigMissing(testContext)) { - return; - } - WireMockConfiguration wireMockConfiguration = wireMockConfiguration(testContext); - if (log.isDebugEnabled()) { - log.debug("WireMock configuration is running [" + wireMockConfiguration.isRunning() + "]"); - } - if (!wireMockConfiguration.isRunning()) { - wireMockConfiguration.init(); - wireMockConfiguration.start(); - WireMockUtils.getMappingsEndpoint(wireMockConfiguration.port()); - } - } catch (Exception e) { - if (log.isDebugEnabled()) { - log.debug("Exception occurred while trying to init WireMock configuration", e); + @Override + public void afterTestClass(TestContext testContext) { + if (wireMockConfigurationMissing(testContext) || annotationMissing(testContext)) { + return; + } + if (portIsFixed(testContext)) { + if (log.isWarnEnabled()) { + log.warn("You've used fixed ports for WireMock setup - " + + "will mark context as dirty. Please use random ports, as much " + + "as possible. Your tests will be faster and more reliable and this" + + "warning will go away"); } + testContext.markApplicationContextDirty(DirtiesContext.HierarchyMode.EXHAUSTIVE); } } - private boolean wireMockConfigMissing(TestContext testContext) { - boolean missing = !testContext.getApplicationContext().containsBean(WireMockConfiguration.class.getName()); + private boolean annotationMissing(TestContext testContext) { + if (testContext.getTestClass() + .getAnnotationsByType(AutoConfigureWireMock.class).length == 0) { + if (log.isDebugEnabled()) { + log.debug("No @AutoConfigureWireMock annotation found on [" + testContext + .getTestClass() + "]. Skipping"); + } + return true; + } + return false; + } + + private boolean wireMockConfigurationMissing(TestContext testContext) { + boolean missing = !testContext.getApplicationContext() + .containsBean(WireMockConfiguration.class.getName()); if (log.isDebugEnabled()) { - log.debug("WireMockConfig is missing [" + missing + "]"); + log.debug("WireMockConfiguration is missing [" + missing + "]"); } return missing; } - @Override public void afterTestClass(TestContext testContext) { - try { - if (wireMockConfigMissing(testContext)) { - return; - } - stopWireMockConfiguration(testContext); - } catch (Exception e) { - if (log.isDebugEnabled()) { - log.debug("Exception occurred while trying to init WireMock configuration", e); - } - } - } - - private void stopWireMockConfiguration(TestContext testContext) { - WireMockConfiguration wireMockConfiguration = wireMockConfiguration(testContext); - if (wireMockConfiguration.isRunning()) { - if (log.isDebugEnabled()) { - log.debug("WireMock is running, will stop it"); - } - wireMockConfiguration.stop(); - } - } - - private WireMockConfiguration wireMockConfiguration(TestContext testContext) { + private WireMockConfiguration wireMockConfig(TestContext testContext) { return testContext.getApplicationContext().getBean(WireMockConfiguration.class); } -} + + private boolean portIsFixed(TestContext testContext) { + WireMockConfiguration wireMockProperties = wireMockConfig(testContext); + int httpPort = wireMockProperties.wireMock.getServer().getPort(); + int httpsPort = wireMockProperties.wireMock.getServer().getHttpsPort(); + return (httpPort != 0 || httpsPort != -1) && httpsPort != 0; + } +} \ No newline at end of file diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockUtils.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockUtils.java deleted file mode 100644 index 5b700ff9c8..0000000000 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockUtils.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2013-2018 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 - * - * http://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.wiremock; - -import java.io.IOException; - -import org.apache.http.HttpHost; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; - -import org.springframework.util.Assert; - -/** - * Utility class to work with WireMock. - * - * @author Marcin Grzejszczak - * @since 2.0.3 - */ -public final class WireMockUtils { - - private WireMockUtils() { - throw new IllegalStateException("Don't instantiate"); - } - - /** - * Thanks to Tom Akehurst: I looked at tcpdump while running the failing - * test. HttpUrlConnection is doing something weird - it's creating a - * connection in a previous test case, which works fine, then the usual - * fin -> fin ack etc. etc. ending handshake happens. But it seems it - * isn't discarded, but reused after that. Because the server thinks - * (rightly) that the connection is closed, it just sends a RST packet. - * Calling the admin endpoint just happened to remove the dead connection - * from the pool. This also fixes the problem (which using the Java HTTP - * client): System.setProperty("http.keepAlive", "false"); - **/ - public static CloseableHttpResponse getMappingsEndpoint(int port) { - CloseableHttpClient client = HttpClientBuilder.create().build(); - try { - CloseableHttpResponse response = client - .execute(new HttpHost("localhost", port), new HttpGet("/__admin/mappings")); - Assert.isTrue(response.getStatusLine().getStatusCode() == 200, "Status code must be 200"); - return response; - } - catch (IOException ex) { - throw new IllegalStateException(ex); - } - } -} diff --git a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockApplicationTests.java b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockApplicationTests.java index 762f7f43f7..6d9ad64428 100644 --- a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockApplicationTests.java +++ b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockApplicationTests.java @@ -1,22 +1,22 @@ package org.springframework.cloud.contract.wiremock; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.junit4.SpringRunner; + import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static org.assertj.core.api.Assertions.assertThat; -import com.github.tomakehurst.wiremock.WireMockServer; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.client.RestTemplate; - @RunWith(SpringRunner.class) -@SpringBootTest(classes=WiremockTestsApplication.class, properties="app.baseUrl=http://localhost:${wiremock.server.port}", webEnvironment=WebEnvironment.NONE) +@SpringBootTest(classes = WiremockTestsApplication.class, + properties = "app.baseUrl=http://localhost:${wiremock.server.port}", webEnvironment = WebEnvironment.NONE) @AutoConfigureWireMock(port = 12345) public class AutoConfigureWireMockApplicationTests { @@ -25,8 +25,8 @@ public class AutoConfigureWireMockApplicationTests { @Test public void contextLoads() throws Exception { - stubFor(get(urlEqualTo("/test")) - .willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("Hello World!"))); + stubFor(get(urlEqualTo("/test")).willReturn(aResponse() + .withHeader("Content-Type", "text/plain").withBody("Hello World!"))); assertThat(this.service.go()).isEqualTo("Hello World!"); } diff --git a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockAutoStubsApplicationTests.java b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockAutoStubsApplicationTests.java index 879c93a7b5..3fa9b88839 100644 --- a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockAutoStubsApplicationTests.java +++ b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockAutoStubsApplicationTests.java @@ -1,17 +1,19 @@ package org.springframework.cloud.contract.wiremock; -import static org.assertj.core.api.Assertions.assertThat; - import org.junit.Test; import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.test.context.junit4.SpringRunner; +import static org.assertj.core.api.Assertions.assertThat; + @RunWith(SpringRunner.class) -@SpringBootTest(classes=WiremockTestsApplication.class, properties="app.baseUrl=http://localhost:${wiremock.server.port}", webEnvironment=WebEnvironment.NONE) -@AutoConfigureWireMock(port=0) +@SpringBootTest(classes = WiremockTestsApplication.class, + properties = "app.baseUrl=http://localhost:${wiremock.server.port}", webEnvironment = WebEnvironment.NONE) +@AutoConfigureWireMock(port = 0) // Default stubs work at classpath:/mappings public class AutoConfigureWireMockAutoStubsApplicationTests { diff --git a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockRandomPortHttpsApplicationTests.java b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockRandomPortHttpsApplicationTests.java index e4dc0bae8e..94ba1a8e72 100644 --- a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockRandomPortHttpsApplicationTests.java +++ b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/AutoConfigureWireMockRandomPortHttpsApplicationTests.java @@ -1,18 +1,19 @@ package org.springframework.cloud.contract.wiremock; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.junit4.SpringRunner; + import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.test.context.junit4.SpringRunner; - @RunWith(SpringRunner.class) @SpringBootTest(classes=WiremockTestsApplication.class, properties="app.baseUrl=https://localhost:${wiremock.server.https-port}", webEnvironment=WebEnvironment.NONE) @AutoConfigureWireMock(port=0, httpsPort=0) @@ -28,4 +29,4 @@ public class AutoConfigureWireMockRandomPortHttpsApplicationTests { assertThat(this.service.go()).isEqualTo("Hello World!"); } -} +} \ No newline at end of file diff --git a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsApplicationTests.java b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsApplicationTests.java index 8fb23892d4..d58fed070a 100644 --- a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsApplicationTests.java +++ b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsApplicationTests.java @@ -10,6 +10,7 @@ import com.github.tomakehurst.wiremock.stubbing.StubMapping; import org.assertj.core.api.BDDAssertions; import org.junit.Test; import org.junit.runner.RunWith; +import wiremock.org.eclipse.jetty.http.HttpStatus; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; @@ -21,7 +22,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -32,8 +32,6 @@ import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.docu import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import wiremock.org.eclipse.jetty.http.HttpStatus; - @RunWith(SpringRunner.class) @SpringBootTest(classes = TestConfiguration.class) @AutoConfigureRestDocs(outputDir = "target/snippets") diff --git a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsMatcherApplicationTests.java b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsMatcherApplicationTests.java index fb0d5a4459..56638861a5 100644 --- a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsMatcherApplicationTests.java +++ b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockServerRestDocsMatcherApplicationTests.java @@ -1,10 +1,12 @@ package org.springframework.cloud.contract.wiremock; +import com.github.tomakehurst.wiremock.client.WireMock; import org.junit.ComparisonFailure; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -23,8 +25,6 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; -import com.github.tomakehurst.wiremock.client.WireMock; - @RunWith(SpringRunner.class) @SpringBootTest(classes = TestConfiguration.class) @AutoConfigureRestDocs(outputDir = "target/snippets")