Commit 1b237de5 authored by Andy Wilkinson's avatar Andy Wilkinson

Use Awaitility in our own tests

Closes gh-18227
parent 568caa12
...@@ -345,6 +345,11 @@ ...@@ -345,6 +345,11 @@
<artifactId>jersey-media-json-jackson</artifactId> <artifactId>jersey-media-json-jackson</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>com.jayway.jsonpath</groupId> <groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId> <artifactId>json-path</artifactId>
......
...@@ -18,8 +18,10 @@ package org.springframework.boot.actuate.management; ...@@ -18,8 +18,10 @@ package org.springframework.boot.actuate.management;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.time.Duration;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest; import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest;
...@@ -31,7 +33,7 @@ import org.springframework.http.MediaType; ...@@ -31,7 +33,7 @@ import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.is;
/** /**
* Integration tests for {@link HeapDumpWebEndpoint} exposed by Jersey, Spring MVC, and * Integration tests for {@link HeapDumpWebEndpoint} exposed by Jersey, Spring MVC, and
...@@ -64,11 +66,7 @@ class HeapDumpWebEndpointWebIntegrationTests { ...@@ -64,11 +66,7 @@ class HeapDumpWebEndpointWebIntegrationTests {
} }
private void assertHeapDumpFileIsDeleted() throws InterruptedException { private void assertHeapDumpFileIsDeleted() throws InterruptedException {
long end = System.currentTimeMillis() + 5000; Awaitility.waitAtMost(Duration.ofSeconds(5)).until(this.endpoint.file::exists, is(false));
while (System.currentTimeMillis() < end && this.endpoint.file.exists()) {
Thread.sleep(100);
}
assertThat(this.endpoint.file.exists()).isFalse();
} }
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
......
...@@ -897,6 +897,11 @@ ...@@ -897,6 +897,11 @@
<artifactId>tomcat-embed-jasper</artifactId> <artifactId>tomcat-embed-jasper</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.hsqldb</groupId> <groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId> <artifactId>hsqldb</artifactId>
......
...@@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.orm.jpa; ...@@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.orm.jpa;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
...@@ -27,6 +28,7 @@ import java.util.HashMap; ...@@ -27,6 +28,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory; import javax.persistence.EntityManagerFactory;
...@@ -37,6 +39,7 @@ import javax.transaction.TransactionManager; ...@@ -37,6 +39,7 @@ import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction; import javax.transaction.UserTransaction;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
import org.awaitility.Awaitility;
import org.hibernate.boot.model.naming.ImplicitNamingStrategy; import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
import org.hibernate.boot.model.naming.PhysicalNamingStrategy; import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.AvailableSettings;
...@@ -77,6 +80,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; ...@@ -77,6 +80,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry; import static org.assertj.core.api.Assertions.entry;
import static org.hamcrest.Matchers.hasSize;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
...@@ -387,12 +391,8 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes ...@@ -387,12 +391,8 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
assertThat(context).hasNotFailed(); assertThat(context).hasNotFailed();
EventCapturingApplicationListener listener = context EventCapturingApplicationListener listener = context
.getBean(EventCapturingApplicationListener.class); .getBean(EventCapturingApplicationListener.class);
long end = System.currentTimeMillis() + 30000; Awaitility.waitAtMost(Duration.ofSeconds(30))
while ((System.currentTimeMillis() < end) && !dataSourceSchemaCreatedEventReceived(listener)) { .until(() -> dataSourceSchemaCreatedEventsReceivedBy(listener), hasSize(1));
Thread.sleep(100);
}
assertThat(listener.events.stream().filter(DataSourceSchemaCreatedEvent.class::isInstance))
.hasSize(1);
}); });
} }
...@@ -408,13 +408,9 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes ...@@ -408,13 +408,9 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
}); });
} }
private boolean dataSourceSchemaCreatedEventReceived(EventCapturingApplicationListener listener) { private List<ApplicationEvent> dataSourceSchemaCreatedEventsReceivedBy(EventCapturingApplicationListener listener) {
for (ApplicationEvent event : listener.events) { return listener.events.stream().filter(DataSourceSchemaCreatedEvent.class::isInstance)
if (event instanceof DataSourceSchemaCreatedEvent) { .collect(Collectors.toList());
return true;
}
}
return false;
} }
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
......
...@@ -130,6 +130,11 @@ ...@@ -130,6 +130,11 @@
<artifactId>HikariCP</artifactId> <artifactId>HikariCP</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId> <artifactId>spring-webmvc</artifactId>
......
...@@ -20,12 +20,14 @@ import java.io.IOException; ...@@ -20,12 +20,14 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.URI; import java.net.URI;
import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.tomcat.websocket.WsWebSocketContainer; import org.apache.tomcat.websocket.WsWebSocketContainer;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
...@@ -43,6 +45,9 @@ import org.springframework.web.socket.client.standard.StandardWebSocketClient; ...@@ -43,6 +45,9 @@ import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.handler.TextWebSocketHandler; import org.springframework.web.socket.handler.TextWebSocketHandler;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
/** /**
* Tests for {@link LiveReloadServer}. * Tests for {@link LiveReloadServer}.
...@@ -107,10 +112,7 @@ class LiveReloadServerTests { ...@@ -107,10 +112,7 @@ class LiveReloadServerTests {
} }
private void awaitClosedException() throws InterruptedException { private void awaitClosedException() throws InterruptedException {
long startTime = System.currentTimeMillis(); Awaitility.waitAtMost(Duration.ofSeconds(10)).until(this.server::getClosedExceptions, is(not(empty())));
while (this.server.getClosedExceptions().isEmpty() && System.currentTimeMillis() - startTime < 10000) {
Thread.sleep(100);
}
} }
@Test @Test
......
...@@ -21,14 +21,19 @@ ...@@ -21,14 +21,19 @@
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<!-- Test --> <!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-support</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>ch.qos.logback</groupId> <groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.awaitility</groupId>
<artifactId>spring-webmvc</artifactId> <artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
...@@ -39,8 +44,8 @@ ...@@ -39,8 +44,8 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-boot-test-support</artifactId> <artifactId>spring-webmvc</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
......
...@@ -21,6 +21,7 @@ import java.io.FileOutputStream; ...@@ -21,6 +21,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
...@@ -28,6 +29,7 @@ import java.util.jar.Attributes; ...@@ -28,6 +29,7 @@ import java.util.jar.Attributes;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import org.assertj.core.api.Condition; import org.assertj.core.api.Condition;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
...@@ -44,6 +46,7 @@ import org.springframework.test.util.ReflectionTestUtils; ...@@ -44,6 +46,7 @@ import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.hamcrest.Matchers.containsString;
/** /**
* Tests for {@link PropertiesLauncher}. * Tests for {@link PropertiesLauncher}.
...@@ -339,14 +342,7 @@ class PropertiesLauncherTests { ...@@ -339,14 +342,7 @@ class PropertiesLauncherTests {
} }
private void waitFor(String value) throws Exception { private void waitFor(String value) throws Exception {
int count = 0; Awaitility.waitAtMost(Duration.ofSeconds(5)).until(this.output::toString, containsString(value));
boolean timeout = false;
while (!timeout && count < 100) {
count++;
Thread.sleep(50L);
timeout = this.output.toString().contains(value);
}
assertThat(timeout).as("Timed out waiting for (" + value + ")").isTrue();
} }
private Condition<Archive> endingWith(String value) { private Condition<Archive> endingWith(String value) {
......
...@@ -421,6 +421,11 @@ ...@@ -421,6 +421,11 @@
<artifactId>httpasyncclient</artifactId> <artifactId>httpasyncclient</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.firebirdsql.jdbc</groupId> <groupId>org.firebirdsql.jdbc</groupId>
<artifactId>jaybird-jdk18</artifactId> <artifactId>jaybird-jdk18</artifactId>
......
...@@ -23,6 +23,7 @@ import java.time.Duration; ...@@ -23,6 +23,7 @@ import java.time.Duration;
import java.util.Arrays; import java.util.Arrays;
import io.undertow.Undertow; import io.undertow.Undertow;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import org.mockito.InOrder; import org.mockito.InOrder;
...@@ -36,6 +37,7 @@ import org.springframework.web.reactive.function.client.WebClient; ...@@ -36,6 +37,7 @@ import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
...@@ -124,11 +126,8 @@ class UndertowReactiveWebServerFactoryTests extends AbstractReactiveWebServerFac ...@@ -124,11 +126,8 @@ class UndertowReactiveWebServerFactoryTests extends AbstractReactiveWebServerFac
assertThat(accessLogDirectory.listFiles()).contains(accessLog); assertThat(accessLogDirectory.listFiles()).contains(accessLog);
} }
private void awaitFile(File file) throws InterruptedException { private void awaitFile(File file) {
long end = System.currentTimeMillis() + 10000; Awaitility.waitAtMost(Duration.ofSeconds(10)).until(file::exists, is(true));
while (!file.exists() && System.currentTimeMillis() < end) {
Thread.sleep(100);
}
} }
} }
...@@ -21,6 +21,7 @@ import java.io.IOException; ...@@ -21,6 +21,7 @@ import java.io.IOException;
import java.net.SocketException; import java.net.SocketException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.time.Duration;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
...@@ -34,6 +35,7 @@ import io.undertow.Undertow.Builder; ...@@ -34,6 +35,7 @@ import io.undertow.Undertow.Builder;
import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.ServletContainer; import io.undertow.servlet.api.ServletContainer;
import org.apache.jasper.servlet.JspServlet; import org.apache.jasper.servlet.JspServlet;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.InOrder; import org.mockito.InOrder;
...@@ -49,6 +51,7 @@ import org.springframework.test.util.ReflectionTestUtils; ...@@ -49,6 +51,7 @@ import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIOException; import static org.assertj.core.api.Assertions.assertThatIOException;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
...@@ -234,11 +237,8 @@ class UndertowServletWebServerFactoryTests extends AbstractServletWebServerFacto ...@@ -234,11 +237,8 @@ class UndertowServletWebServerFactoryTests extends AbstractServletWebServerFacto
return null; // Undertow does not support JSPs return null; // Undertow does not support JSPs
} }
private void awaitFile(File file) throws InterruptedException { private void awaitFile(File file) {
long end = System.currentTimeMillis() + 10000; Awaitility.waitAtMost(Duration.ofSeconds(10)).until(file::exists, is(true));
while (!file.exists() && System.currentTimeMillis() < end) {
Thread.sleep(100);
}
} }
private ServletContainer getServletContainerFromNewFactory() { private ServletContainer getServletContainerFromNewFactory() {
......
...@@ -21,6 +21,10 @@ ...@@ -21,6 +21,10 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
...@@ -32,8 +36,9 @@ ...@@ -32,8 +36,9 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.bytebuddy</groupId> <groupId>org.awaitility</groupId>
<artifactId>byte-buddy</artifactId> <artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
......
/*
* Copyright 2012-2019 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.boot.devtools.tests;
import java.io.File;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.testsupport.BuildOutput;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Base class for DevTools integration tests.
*
* @author Andy Wilkinson
*/
abstract class AbstractDevToolsIntegrationTests {
protected static final BuildOutput buildOutput = new BuildOutput(AbstractDevToolsIntegrationTests.class);
protected final File serverPortFile = new File(buildOutput.getRootLocation(), "server.port");
@RegisterExtension
protected final JvmLauncher javaLauncher = new JvmLauncher();
@TempDir
protected static File temp;
protected LaunchedApplication launchedApplication;
protected void launchApplication(ApplicationLauncher applicationLauncher, String... args) throws Exception {
this.serverPortFile.delete();
this.launchedApplication = applicationLauncher.launchApplication(this.javaLauncher, this.serverPortFile, args);
}
@AfterEach
void stopApplication() throws InterruptedException {
this.launchedApplication.stop();
}
protected int awaitServerPort() throws Exception {
int port = Awaitility.waitAtMost(Duration.ofSeconds(30))
.until(() -> new ApplicationState(this.serverPortFile, this.launchedApplication),
ApplicationState::hasServerPort)
.getServerPort();
this.serverPortFile.delete();
System.out.println("Got port " + port);
this.launchedApplication.restartRemote(port);
Thread.sleep(1000);
return port;
}
protected ControllerBuilder controller(String name) {
return new ControllerBuilder(name, this.launchedApplication.getClassesDirectory());
}
protected static final class ControllerBuilder {
private final List<String> mappings = new ArrayList<>();
private final String name;
private final File classesDirectory;
protected ControllerBuilder(String name, File classesDirectory) {
this.name = name;
this.classesDirectory = classesDirectory;
}
protected ControllerBuilder withRequestMapping(String mapping) {
this.mappings.add(mapping);
return this;
}
protected void build() throws Exception {
DynamicType.Builder<Object> builder = new ByteBuddy().subclass(Object.class).name(this.name)
.annotateType(AnnotationDescription.Builder.ofType(RestController.class).build());
for (String mapping : this.mappings) {
builder = builder.defineMethod(mapping, String.class, Visibility.PUBLIC)
.intercept(FixedValue.value(mapping)).annotateMethod(AnnotationDescription.Builder
.ofType(RequestMapping.class).defineArray("value", mapping).build());
}
builder.make().saveIn(this.classesDirectory);
}
}
}
/*
* Copyright 2012-2019 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.boot.devtools.tests;
import java.io.File;
import org.springframework.boot.devtools.tests.JvmLauncher.LaunchedJvm;
/**
* State of an application.
*
* @author Andy Wilkinson
*/
final class ApplicationState {
private final Integer serverPort;
private final FileContents out;
private final FileContents err;
ApplicationState(File serverPortFile, LaunchedJvm jvm) {
this(serverPortFile, jvm.getStandardOut(), jvm.getStandardError());
}
ApplicationState(File serverPortFile, LaunchedApplication application) {
this(serverPortFile, application.getStandardOut(), application.getStandardError());
}
private ApplicationState(File serverPortFile, File out, File err) {
this.serverPort = new FileContents(serverPortFile).get(Integer::parseInt);
this.out = new FileContents(out);
this.err = new FileContents(err);
}
boolean hasServerPort() {
return this.serverPort != null;
}
int getServerPort() {
return this.serverPort;
}
@Override
public String toString() {
return String.format("Application output:%n%s%n%s", this.out, this.err);
}
}
...@@ -17,29 +17,13 @@ ...@@ -17,29 +17,13 @@
package org.springframework.boot.devtools.tests; package org.springframework.boot.devtools.tests;
import java.io.File; import java.io.File;
import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.DynamicType.Builder;
import net.bytebuddy.implementation.FixedValue;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.testsupport.BuildOutput;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -48,29 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -48,29 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class DevToolsIntegrationTests { public class DevToolsIntegrationTests extends AbstractDevToolsIntegrationTests {
@TempDir
static File temp;
private static final BuildOutput buildOutput = new BuildOutput(DevToolsIntegrationTests.class);
private LaunchedApplication launchedApplication;
private final File serverPortFile = new File(buildOutput.getRootLocation(), "server.port");
@RegisterExtension
final JvmLauncher javaLauncher = new JvmLauncher();
private void launchApplication(ApplicationLauncher applicationLauncher) throws Exception {
this.serverPortFile.delete();
this.launchedApplication = applicationLauncher.launchApplication(this.javaLauncher, this.serverPortFile);
}
@AfterEach
void stopApplication() throws InterruptedException {
this.launchedApplication.stop();
}
@ParameterizedTest(name = "{0}") @ParameterizedTest(name = "{0}")
@MethodSource("parameters") @MethodSource("parameters")
...@@ -191,33 +153,6 @@ public class DevToolsIntegrationTests { ...@@ -191,33 +153,6 @@ public class DevToolsIntegrationTests {
.isEqualTo(HttpStatus.NOT_FOUND); .isEqualTo(HttpStatus.NOT_FOUND);
} }
private int awaitServerPort() throws Exception {
Duration timeToWait = Duration.ofSeconds(40);
long end = System.currentTimeMillis() + timeToWait.toMillis();
System.out.println("Reading server port from '" + this.serverPortFile + "'");
while (this.serverPortFile.length() == 0) {
if (System.currentTimeMillis() > end) {
throw new IllegalStateException(String.format(
"server.port file '" + this.serverPortFile + "' was not written within " + timeToWait.toMillis()
+ "ms. Application output:%n%s%s",
FileCopyUtils.copyToString(new FileReader(this.launchedApplication.getStandardOut())),
FileCopyUtils.copyToString(new FileReader(this.launchedApplication.getStandardError()))));
}
Thread.sleep(100);
}
FileReader portReader = new FileReader(this.serverPortFile);
int port = Integer.valueOf(FileCopyUtils.copyToString(portReader));
this.serverPortFile.delete();
System.out.println("Got port " + port);
this.launchedApplication.restartRemote(port);
Thread.sleep(1000);
return port;
}
private ControllerBuilder controller(String name) {
return new ControllerBuilder(name, this.launchedApplication.getClassesDirectory());
}
static Object[] parameters() throws IOException { static Object[] parameters() throws IOException {
Directories directories = new Directories(buildOutput, temp); Directories directories = new Directories(buildOutput, temp);
return new Object[] { new Object[] { new LocalApplicationLauncher(directories) }, return new Object[] { new Object[] { new LocalApplicationLauncher(directories) },
...@@ -225,35 +160,4 @@ public class DevToolsIntegrationTests { ...@@ -225,35 +160,4 @@ public class DevToolsIntegrationTests {
new Object[] { new JarFileRemoteApplicationLauncher(directories) } }; new Object[] { new JarFileRemoteApplicationLauncher(directories) } };
} }
private static final class ControllerBuilder {
private final List<String> mappings = new ArrayList<>();
private final String name;
private final File classesDirectory;
private ControllerBuilder(String name, File classesDirectory) {
this.name = name;
this.classesDirectory = classesDirectory;
}
ControllerBuilder withRequestMapping(String mapping) {
this.mappings.add(mapping);
return this;
}
void build() throws Exception {
Builder<Object> builder = new ByteBuddy().subclass(Object.class).name(this.name)
.annotateType(AnnotationDescription.Builder.ofType(RestController.class).build());
for (String mapping : this.mappings) {
builder = builder.defineMethod(mapping, String.class, Visibility.PUBLIC)
.intercept(FixedValue.value(mapping)).annotateMethod(AnnotationDescription.Builder
.ofType(RequestMapping.class).defineArray("value", mapping).build());
}
builder.make().saveIn(this.classesDirectory);
}
}
} }
...@@ -16,30 +16,13 @@ ...@@ -16,30 +16,13 @@
package org.springframework.boot.devtools.tests; package org.springframework.boot.devtools.tests;
import java.io.File;
import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.testsupport.BuildOutput;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -48,36 +31,13 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -48,36 +31,13 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Madhura Bhave * @author Madhura Bhave
*/ */
public class DevToolsWithLazyInitializationIntegrationTests { public class DevToolsWithLazyInitializationIntegrationTests extends AbstractDevToolsIntegrationTests {
@TempDir
static File temp;
private static final BuildOutput buildOutput = new BuildOutput(DevToolsIntegrationTests.class);
private LaunchedApplication launchedApplication;
private final File serverPortFile = new File(buildOutput.getRootLocation(), "server.port");
@RegisterExtension
final JvmLauncher javaLauncher = new JvmLauncher();
private void launchApplication(ApplicationLauncher applicationLauncher) throws Exception {
this.serverPortFile.delete();
this.launchedApplication = applicationLauncher.launchApplication(this.javaLauncher, this.serverPortFile,
"--spring.main.lazy-initialization=true");
}
@AfterEach
void stopApplication() throws InterruptedException {
this.launchedApplication.stop();
}
@ParameterizedTest(name = "{0}") @ParameterizedTest(name = "{0}")
@MethodSource("parameters") @MethodSource("parameters")
public void addARequestMappingToAnExistingControllerWhenLazyInit(ApplicationLauncher applicationLauncher) public void addARequestMappingToAnExistingControllerWhenLazyInit(ApplicationLauncher applicationLauncher)
throws Exception { throws Exception {
launchApplication(applicationLauncher); launchApplication(applicationLauncher, "--spring.main.lazy-initialization=true");
TestRestTemplate template = new TestRestTemplate(); TestRestTemplate template = new TestRestTemplate();
String urlBase = "http://localhost:" + awaitServerPort(); String urlBase = "http://localhost:" + awaitServerPort();
assertThat(template.getForObject(urlBase + "/one", String.class)).isEqualTo("one"); assertThat(template.getForObject(urlBase + "/one", String.class)).isEqualTo("one");
...@@ -89,33 +49,6 @@ public class DevToolsWithLazyInitializationIntegrationTests { ...@@ -89,33 +49,6 @@ public class DevToolsWithLazyInitializationIntegrationTests {
assertThat(template.getForObject(urlBase + "/two", String.class)).isEqualTo("two"); assertThat(template.getForObject(urlBase + "/two", String.class)).isEqualTo("two");
} }
private int awaitServerPort() throws Exception {
Duration timeToWait = Duration.ofSeconds(40);
long end = System.currentTimeMillis() + timeToWait.toMillis();
System.out.println("Reading server port from '" + this.serverPortFile + "'");
while (this.serverPortFile.length() == 0) {
if (System.currentTimeMillis() > end) {
throw new IllegalStateException(String.format(
"server.port file '" + this.serverPortFile + "' was not written within " + timeToWait.toMillis()
+ "ms. Application output:%n%s%s",
FileCopyUtils.copyToString(new FileReader(this.launchedApplication.getStandardOut())),
FileCopyUtils.copyToString(new FileReader(this.launchedApplication.getStandardError()))));
}
Thread.sleep(100);
}
FileReader portReader = new FileReader(this.serverPortFile);
int port = Integer.valueOf(FileCopyUtils.copyToString(portReader));
this.serverPortFile.delete();
System.out.println("Got port " + port);
this.launchedApplication.restartRemote(port);
Thread.sleep(1000);
return port;
}
private ControllerBuilder controller(String name) {
return new ControllerBuilder(name, this.launchedApplication.getClassesDirectory());
}
static Object[] parameters() throws IOException { static Object[] parameters() throws IOException {
Directories directories = new Directories(buildOutput, temp); Directories directories = new Directories(buildOutput, temp);
return new Object[] { new Object[] { new LocalApplicationLauncher(directories) }, return new Object[] { new Object[] { new LocalApplicationLauncher(directories) },
...@@ -124,35 +57,4 @@ public class DevToolsWithLazyInitializationIntegrationTests { ...@@ -124,35 +57,4 @@ public class DevToolsWithLazyInitializationIntegrationTests {
} }
private static final class ControllerBuilder {
private final List<String> mappings = new ArrayList<>();
private final String name;
private final File classesDirectory;
private ControllerBuilder(String name, File classesDirectory) {
this.name = name;
this.classesDirectory = classesDirectory;
}
ControllerBuilder withRequestMapping(String mapping) {
this.mappings.add(mapping);
return this;
}
void build() throws Exception {
DynamicType.Builder<Object> builder = new ByteBuddy().subclass(Object.class).name(this.name)
.annotateType(AnnotationDescription.Builder.ofType(RestController.class).build());
for (String mapping : this.mappings) {
builder = builder.defineMethod(mapping, String.class, Visibility.PUBLIC)
.intercept(FixedValue.value(mapping)).annotateMethod(AnnotationDescription.Builder
.ofType(RequestMapping.class).defineArray("value", mapping).build());
}
builder.make().saveIn(this.classesDirectory);
}
}
} }
/*
* Copyright 2012-2019 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.boot.devtools.tests;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.function.Function;
import org.springframework.util.FileCopyUtils;
/**
* Provides access to the contents of a file.
*
* @author Andy Wilkinson
*/
class FileContents {
private final File file;
FileContents(File file) {
this.file = file;
}
String get() {
return get(Function.identity());
}
<T> T get(Function<String, T> transformer) {
if ((!this.file.exists()) || this.file.length() == 0) {
return null;
}
try {
return transformer.apply(FileCopyUtils.copyToString(new FileReader(this.file)));
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
@Override
public String toString() {
return get();
}
}
...@@ -17,17 +17,20 @@ ...@@ -17,17 +17,20 @@
package org.springframework.boot.devtools.tests; package org.springframework.boot.devtools.tests;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import org.awaitility.Awaitility;
import org.springframework.boot.devtools.RemoteSpringApplication; import org.springframework.boot.devtools.RemoteSpringApplication;
import org.springframework.boot.devtools.tests.JvmLauncher.LaunchedJvm; import org.springframework.boot.devtools.tests.JvmLauncher.LaunchedJvm;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import static org.hamcrest.Matchers.containsString;
/** /**
* Base class for {@link ApplicationLauncher} implementations that use * Base class for {@link ApplicationLauncher} implementations that use
* {@link RemoteSpringApplication}. * {@link RemoteSpringApplication}.
...@@ -45,7 +48,7 @@ abstract class RemoteApplicationLauncher extends AbstractApplicationLauncher { ...@@ -45,7 +48,7 @@ abstract class RemoteApplicationLauncher extends AbstractApplicationLauncher {
LaunchedJvm applicationJvm = javaLauncher.launch("app", createApplicationClassPath(), LaunchedJvm applicationJvm = javaLauncher.launch("app", createApplicationClassPath(),
"com.example.DevToolsTestApplication", serverPortFile.getAbsolutePath(), "--server.port=0", "com.example.DevToolsTestApplication", serverPortFile.getAbsolutePath(), "--server.port=0",
"--spring.devtools.remote.secret=secret"); "--spring.devtools.remote.secret=secret");
int port = awaitServerPort(applicationJvm.getStandardOut(), serverPortFile); int port = awaitServerPort(applicationJvm, serverPortFile);
BiFunction<Integer, File, Process> remoteRestarter = getRemoteRestarter(javaLauncher); BiFunction<Integer, File, Process> remoteRestarter = getRemoteRestarter(javaLauncher);
return new LaunchedApplication(getDirectories().getRemoteAppDirectory(), applicationJvm.getStandardOut(), return new LaunchedApplication(getDirectories().getRemoteAppDirectory(), applicationJvm.getStandardOut(),
applicationJvm.getStandardError(), applicationJvm.getProcess(), remoteRestarter.apply(port, null), applicationJvm.getStandardError(), applicationJvm.getProcess(), remoteRestarter.apply(port, null),
...@@ -60,7 +63,7 @@ abstract class RemoteApplicationLauncher extends AbstractApplicationLauncher { ...@@ -60,7 +63,7 @@ abstract class RemoteApplicationLauncher extends AbstractApplicationLauncher {
args.addAll(Arrays.asList(additionalArgs)); args.addAll(Arrays.asList(additionalArgs));
LaunchedJvm applicationJvm = javaLauncher.launch("app", createApplicationClassPath(), LaunchedJvm applicationJvm = javaLauncher.launch("app", createApplicationClassPath(),
args.toArray(new String[] {})); args.toArray(new String[] {}));
int port = awaitServerPort(applicationJvm.getStandardOut(), serverPortFile); int port = awaitServerPort(applicationJvm, serverPortFile);
BiFunction<Integer, File, Process> remoteRestarter = getRemoteRestarter(javaLauncher); BiFunction<Integer, File, Process> remoteRestarter = getRemoteRestarter(javaLauncher);
return new LaunchedApplication(getDirectories().getRemoteAppDirectory(), applicationJvm.getStandardOut(), return new LaunchedApplication(getDirectories().getRemoteAppDirectory(), applicationJvm.getStandardOut(),
applicationJvm.getStandardError(), applicationJvm.getProcess(), remoteRestarter.apply(port, null), applicationJvm.getStandardError(), applicationJvm.getProcess(), remoteRestarter.apply(port, null),
...@@ -96,35 +99,16 @@ abstract class RemoteApplicationLauncher extends AbstractApplicationLauncher { ...@@ -96,35 +99,16 @@ abstract class RemoteApplicationLauncher extends AbstractApplicationLauncher {
return StringUtils.collectionToDelimitedString(entries, File.pathSeparator); return StringUtils.collectionToDelimitedString(entries, File.pathSeparator);
} }
private int awaitServerPort(File standardOut, File serverPortFile) throws Exception { private int awaitServerPort(LaunchedJvm jvm, File serverPortFile) throws Exception {
long end = System.currentTimeMillis() + 30000; return Awaitility.waitAtMost(Duration.ofSeconds(30))
while (serverPortFile.length() == 0) { .until(() -> new ApplicationState(serverPortFile, jvm), ApplicationState::hasServerPort)
if (System.currentTimeMillis() > end) { .getServerPort();
throw new IllegalStateException(
String.format("server.port file was not written within 30 seconds. Application output:%n%s",
FileCopyUtils.copyToString(new FileReader(standardOut))));
}
Thread.sleep(100);
}
FileReader portReader = new FileReader(serverPortFile);
int port = Integer.valueOf(FileCopyUtils.copyToString(portReader));
return port;
} }
private void awaitRemoteSpringApplication(File standardOut) throws Exception { private void awaitRemoteSpringApplication(File standardOut) throws Exception {
long end = System.currentTimeMillis() + 30000; FileContents contents = new FileContents(standardOut);
while (!standardOut.exists()) { Awaitility.waitAtMost(Duration.ofSeconds(30)).until(contents::get,
if (System.currentTimeMillis() > end) { containsString("Started RemoteSpringApplication"));
throw new IllegalStateException("Standard out file was not written within 30 seconds");
}
Thread.sleep(100);
}
while (!FileCopyUtils.copyToString(new FileReader(standardOut)).contains("Started RemoteSpringApplication")) {
if (System.currentTimeMillis() > end) {
throw new IllegalStateException("RemoteSpringApplication did not start within 30 seconds");
}
Thread.sleep(100);
}
} }
} }
...@@ -45,6 +45,11 @@ ...@@ -45,6 +45,11 @@
<version>3.0.0</version> <version>3.0.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId> <artifactId>jetty-webapp</artifactId>
......
...@@ -21,9 +21,11 @@ import java.io.FileReader; ...@@ -21,9 +21,11 @@ import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext;
...@@ -93,16 +95,12 @@ abstract class AbstractApplicationLauncher implements BeforeEachCallback, AfterE ...@@ -93,16 +95,12 @@ abstract class AbstractApplicationLauncher implements BeforeEachCallback, AfterE
} }
private int awaitServerPort(Process process, File serverPortFile) throws Exception { private int awaitServerPort(Process process, File serverPortFile) throws Exception {
long end = System.currentTimeMillis() + 30000; Awaitility.waitAtMost(Duration.ofSeconds(30)).until(serverPortFile::length, (length) -> {
while (serverPortFile.length() == 0) {
if (System.currentTimeMillis() > end) {
throw new IllegalStateException("server.port file was not written within 30 seconds");
}
if (!process.isAlive()) { if (!process.isAlive()) {
throw new IllegalStateException("Application failed to launch"); throw new IllegalStateException("Application failed to start");
} }
Thread.sleep(100); return length > 0;
} });
return Integer.parseInt(FileCopyUtils.copyToString(new FileReader(serverPortFile))); return Integer.parseInt(FileCopyUtils.copyToString(new FileReader(serverPortFile)));
} }
......
...@@ -35,6 +35,11 @@ ...@@ -35,6 +35,11 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
......
...@@ -20,11 +20,9 @@ import java.io.File; ...@@ -20,11 +20,9 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.concurrent.Callable; import java.time.Duration;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
...@@ -39,7 +37,7 @@ import org.springframework.core.io.Resource; ...@@ -39,7 +37,7 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.util.StreamUtils; import org.springframework.util.StreamUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString;
/** /**
* Basic integration tests for service demo application. * Basic integration tests for service demo application.
...@@ -66,8 +64,7 @@ class SampleIntegrationApplicationTests { ...@@ -66,8 +64,7 @@ class SampleIntegrationApplicationTests {
"--service.output-dir=" + outputDir); "--service.output-dir=" + outputDir);
SpringApplication.run(ProducerApplication.class, "World", "--service.input-dir=" + inputDir, SpringApplication.run(ProducerApplication.class, "World", "--service.input-dir=" + inputDir,
"--service.output-dir=" + outputDir); "--service.output-dir=" + outputDir);
String output = getOutput(outputDir); awaitOutputContaining(outputDir, "Hello World");
assertThat(output).contains("Hello World");
} }
@Test @Test
...@@ -76,41 +73,35 @@ class SampleIntegrationApplicationTests { ...@@ -76,41 +73,35 @@ class SampleIntegrationApplicationTests {
File outputDir = new File(temp.toFile(), "output"); File outputDir = new File(temp.toFile(), "output");
this.context = SpringApplication.run(SampleIntegrationApplication.class, "testviamg", this.context = SpringApplication.run(SampleIntegrationApplication.class, "testviamg",
"--service.input-dir=" + inputDir, "--service.output-dir=" + outputDir); "--service.input-dir=" + inputDir, "--service.output-dir=" + outputDir);
String output = getOutput(this.context.getBean(ServiceProperties.class).getOutputDir()); awaitOutputContaining(this.context.getBean(ServiceProperties.class).getOutputDir(), "testviamg");
assertThat(output).contains("testviamg");
} }
private String getOutput(File outputDir) throws Exception { private void awaitOutputContaining(File outputDir, String requiredContents) throws Exception {
Future<String> future = Executors.newSingleThreadExecutor().submit(new Callable<String>() { Awaitility.waitAtMost(Duration.ofSeconds(30)).until(() -> outputIn(outputDir),
@Override containsString(requiredContents));
public String call() throws Exception { }
Resource[] resources = getResourcesWithContent(outputDir);
while (resources.length == 0) { private String outputIn(File outputDir) throws IOException {
Thread.sleep(200); Resource[] resources = findResources(outputDir);
resources = getResourcesWithContent(outputDir); if (resources.length == 0) {
} return null;
StringBuilder builder = new StringBuilder(); }
for (Resource resource : resources) { return readResources(resources);
try (InputStream inputStream = resource.getInputStream()) { }
builder.append(new String(StreamUtils.copyToByteArray(inputStream)));
} private Resource[] findResources(File outputDir) throws IOException {
} return ResourcePatternUtils.getResourcePatternResolver(new DefaultResourceLoader())
return builder.toString(); .getResources("file:" + outputDir.getAbsolutePath() + "/*.txt");
}
});
return future.get(30, TimeUnit.SECONDS);
} }
private Resource[] getResourcesWithContent(File outputDir) throws IOException { private String readResources(Resource[] resources) throws IOException {
Resource[] candidates = ResourcePatternUtils.getResourcePatternResolver(new DefaultResourceLoader()) StringBuilder builder = new StringBuilder();
.getResources("file:" + outputDir.getAbsolutePath() + "/**"); for (Resource resource : resources) {
for (Resource candidate : candidates) { try (InputStream input = resource.getInputStream()) {
if ((candidate.getFilename() != null && candidate.getFilename().endsWith(".writing")) builder.append(new String(StreamUtils.copyToByteArray(input)));
|| candidate.contentLength() == 0) {
return new Resource[0];
} }
} }
return candidates; return builder.toString();
} }
} }
...@@ -29,6 +29,11 @@ ...@@ -29,6 +29,11 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.kafka</groupId> <groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka-test</artifactId> <artifactId>spring-kafka-test</artifactId>
......
...@@ -15,6 +15,9 @@ ...@@ -15,6 +15,9 @@
*/ */
package smoketest.kafka; package smoketest.kafka;
import java.time.Duration;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -22,6 +25,8 @@ import org.springframework.boot.test.context.SpringBootTest; ...@@ -22,6 +25,8 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.kafka.test.context.EmbeddedKafka; import org.springframework.kafka.test.context.EmbeddedKafka;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.not;
/** /**
* Integration tests for demo application. * Integration tests for demo application.
...@@ -39,10 +44,7 @@ class SampleKafkaApplicationTests { ...@@ -39,10 +44,7 @@ class SampleKafkaApplicationTests {
@Test @Test
void testVanillaExchange() throws Exception { void testVanillaExchange() throws Exception {
long end = System.currentTimeMillis() + 10000; Awaitility.waitAtMost(Duration.ofSeconds(30)).until(this.consumer::getMessages, not(empty()));
while (this.consumer.getMessages().isEmpty() && System.currentTimeMillis() < end) {
Thread.sleep(250);
}
assertThat(this.consumer.getMessages()).extracting("message").containsOnly("A simple test message"); assertThat(this.consumer.getMessages()).extracting("message").containsOnly("A simple test message");
} }
......
...@@ -34,6 +34,11 @@ ...@@ -34,6 +34,11 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
......
...@@ -20,7 +20,9 @@ import java.io.File; ...@@ -20,7 +20,9 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Duration;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import smoketest.parent.SampleParentContextApplication; import smoketest.parent.SampleParentContextApplication;
...@@ -33,7 +35,7 @@ import org.springframework.core.io.Resource; ...@@ -33,7 +35,7 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.util.StreamUtils; import org.springframework.util.StreamUtils;
import static org.junit.jupiter.api.Assertions.fail; import static org.hamcrest.Matchers.containsString;
/** /**
* Basic integration tests for service demo application. * Basic integration tests for service demo application.
...@@ -65,26 +67,16 @@ class SampleIntegrationParentApplicationTests { ...@@ -65,26 +67,16 @@ class SampleIntegrationParentApplicationTests {
} }
private void awaitOutputContaining(File outputDir, String requiredContents) throws Exception { private void awaitOutputContaining(File outputDir, String requiredContents) throws Exception {
long endTime = System.currentTimeMillis() + 30000; Awaitility.waitAtMost(Duration.ofSeconds(30)).until(() -> outputIn(outputDir),
String output = null; containsString(requiredContents));
while (System.currentTimeMillis() < endTime) { }
Resource[] resources = findResources(outputDir);
if (resources.length == 0) { private String outputIn(File outputDir) throws IOException {
Thread.sleep(200); Resource[] resources = findResources(outputDir);
resources = findResources(outputDir); if (resources.length == 0) {
} return null;
else {
output = readResources(resources);
if (output != null && output.contains(requiredContents)) {
return;
}
else {
Thread.sleep(200);
output = readResources(resources);
}
}
} }
fail("Timed out awaiting output containing '" + requiredContents + "'. Output was '" + output + "'"); return readResources(resources);
} }
private Resource[] findResources(File outputDir) throws IOException { private Resource[] findResources(File outputDir) throws IOException {
......
...@@ -35,6 +35,11 @@ ...@@ -35,6 +35,11 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
......
...@@ -16,6 +16,9 @@ ...@@ -16,6 +16,9 @@
package smoketest.quartz; package smoketest.quartz;
import java.time.Duration;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
...@@ -24,7 +27,7 @@ import org.springframework.boot.test.system.CapturedOutput; ...@@ -24,7 +27,7 @@ import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString;
/** /**
* Tests for {@link SampleQuartzApplication}. * Tests for {@link SampleQuartzApplication}.
...@@ -37,11 +40,7 @@ class SampleQuartzApplicationTests { ...@@ -37,11 +40,7 @@ class SampleQuartzApplicationTests {
@Test @Test
void quartzJobIsTriggered(CapturedOutput output) throws InterruptedException { void quartzJobIsTriggered(CapturedOutput output) throws InterruptedException {
try (ConfigurableApplicationContext context = SpringApplication.run(SampleQuartzApplication.class)) { try (ConfigurableApplicationContext context = SpringApplication.run(SampleQuartzApplication.class)) {
long end = System.currentTimeMillis() + 5000; Awaitility.waitAtMost(Duration.ofSeconds(5)).until(output::toString, containsString("Hello World!"));
while ((!output.toString().contains("Hello World!")) && System.currentTimeMillis() < end) {
Thread.sleep(100);
}
assertThat(output).contains("Hello World!");
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment