/*
* Copyright 2012-2017 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.boot.devtools.tests;
import java.io.File;
import java.io.FileReader;
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.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.boot.test.web.client.TestRestTemplate;
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;
/**
* Integration tests for DevTools.
*
* @author Andy Wilkinson
*/
@RunWith(Parameterized.class)
public class DevToolsIntegrationTests {
private LaunchedApplication launchedApplication;
private final File serverPortFile = new File("target/server.port");
private final ApplicationLauncher applicationLauncher;
@Rule
public JvmLauncher javaLauncher = new JvmLauncher();
@Parameters(name = "{0}")
public static Object[] parameters() {
return new Object[] { new Object[] { new LocalApplicationLauncher() },
new Object[] { new ExplodedRemoteApplicationLauncher() },
new Object[] { new JarFileRemoteApplicationLauncher() } };
}
public DevToolsIntegrationTests(ApplicationLauncher applicationLauncher) {
this.applicationLauncher = applicationLauncher;
}
@Before
public void launchApplication() throws Exception {
this.serverPortFile.delete();
this.launchedApplication = this.applicationLauncher
.launchApplication(this.javaLauncher);
}
@After
public void stopApplication() {
this.launchedApplication.stop();
}
@Test
public void addARequestMappingToAnExistingController() throws Exception {
TestRestTemplate template = new TestRestTemplate();
String urlBase = "http://localhost:" + awaitServerPort();
assertThat(template.getForObject(urlBase + "/one", String.class))
.isEqualTo("one");
assertThat(template.getForEntity(urlBase + "/two", String.class).getStatusCode())
.isEqualTo(HttpStatus.NOT_FOUND);
controller("com.example.ControllerOne").withRequestMapping("one")
.withRequestMapping("two").build();
assertThat(template.getForObject(urlBase + "/one", String.class))
.isEqualTo("one");
assertThat(template.getForObject("http://localhost:" + awaitServerPort() + "/two",
String.class)).isEqualTo("two");
}
@Test
public void removeARequestMappingFromAnExistingController() throws Exception {
TestRestTemplate template = new TestRestTemplate();
assertThat(template.getForObject("http://localhost:" + awaitServerPort() + "/one",
String.class)).isEqualTo("one");
controller("com.example.ControllerOne").build();
assertThat(template.getForEntity("http://localhost:" + awaitServerPort() + "/one",
String.class).getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
@Test
public void createAController() throws Exception {
TestRestTemplate template = new TestRestTemplate();
String urlBase = "http://localhost:" + awaitServerPort();
assertThat(template.getForObject(urlBase + "/one", String.class))
.isEqualTo("one");
assertThat(template.getForEntity(urlBase + "/two", String.class).getStatusCode())
.isEqualTo(HttpStatus.NOT_FOUND);
controller("com.example.ControllerTwo").withRequestMapping("two").build();
assertThat(template.getForObject(urlBase + "/one", String.class))
.isEqualTo("one");
assertThat(template.getForObject("http://localhost:" + awaitServerPort() + "/two",
String.class)).isEqualTo("two");
}
@Test
public void deleteAController() throws Exception {
TestRestTemplate template = new TestRestTemplate();
assertThat(template.getForObject("http://localhost:" + awaitServerPort() + "/one",
String.class)).isEqualTo("one");
assertThat(new File(this.launchedApplication.getClassesDirectory(),
"com/example/ControllerOne.class").delete()).isTrue();
assertThat(template.getForEntity("http://localhost:" + awaitServerPort() + "/one",
String.class).getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
private int awaitServerPort() throws Exception {
long end = System.currentTimeMillis() + 30000;
while (this.serverPortFile.length() == 0) {
if (System.currentTimeMillis() > end) {
throw new IllegalStateException(String.format(
"server.port file was not written within 30 seconds. "
+ "Application output:%n%s",
FileCopyUtils.copyToString(new FileReader(
this.launchedApplication.getStandardOut()))));
}
Thread.sleep(100);
}
FileReader portReader = new FileReader(this.serverPortFile);
int port = Integer.valueOf(FileCopyUtils.copyToString(portReader));
this.serverPortFile.delete();
return port;
}
private ControllerBuilder controller(String name) {
return new ControllerBuilder(name,
this.launchedApplication.getClassesDirectory());
}
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;
}
public ControllerBuilder withRequestMapping(String mapping) {
this.mappings.add(mapping);
return this;
}
public 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);
}
}
}