Relocate test projects under spring-boot-tests

Move integration and deployment tests under a single `spring-boot-tests`
module.

See gh-9316
This commit is contained in:
Phillip Webb
2017-09-28 22:52:53 -07:00
parent 0ba4830b4f
commit 89b0ba2c14
85 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
<relativePath>../spring-boot-parent</relativePath>
</parent>
<artifactId>spring-boot-integration-tests</artifactId>
<packaging>pom</packaging>
<name>Spring Boot Integration Tests</name>
<description>Spring Boot Integration Tests</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/..</main.basedir>
<java.version>1.8</java.version>
</properties>
<modules>
<module>spring-boot-devtools-tests</module>
<module>spring-boot-integration-tests-embedded-servlet-container</module>
<module>spring-boot-launch-script-tests</module>
<module>spring-boot-security-tests</module>
</modules>
<profiles>
<profile>
<id>default</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<!-- Invoke integration tests in the install phase, after the maven-plugin
is available -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-invoker-plugin</artifactId>
<inherited>false</inherited>
<configuration>
<settingsFile>src/it/settings.xml</settingsFile>
<projectsDirectory>${main.basedir}/spring-boot-samples/</projectsDirectory>
<localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath>
<skipInvocation>${skipTests}</skipInvocation>
<streamLogs>true</streamLogs>
<profiles>
<profile>integration-test</profile>
</profiles>
<localRepositoryPath> </localRepositoryPath>
<pomIncludes>
<pomInclude>*/pom.xml</pomInclude>
</pomIncludes>
</configuration>
<executions>
<execution>
<id>integration-test</id>
<phase>install</phase>
<goals>
<goal>install</goal>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<inherited>false</inherited>
<executions>
<execution>
<id>clean-samples</id>
<phase>clean</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<delete includeemptydirs="true">
<fileset dir="${main.basedir}/spring-boot-samples" includes="**/target/" />
</delete>
</target>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<inherited>false</inherited>
<executions>
<execution>
<id>clean-samples</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>full</id>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-integration-tests</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-devtools-tests</artifactId>
<name>Spring Boot DevTools Tests</name>
<description>${project.name}</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>process-test-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeScope>runtime</includeScope>
<outputDirectory>${project.build.directory}/dependencies</outputDirectory>
<overWriteSnapshots>true</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<versionRange>[2.10,)</versionRange>
<goals>
<goal>copy-dependencies</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2012-2016 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 com.example;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ControllerOne {
@RequestMapping("/one")
public String one() {
return "one";
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2012-2016 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 com.example;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.system.EmbeddedServerPortFileWriter;
@SpringBootApplication
public class DevToolsTestApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(DevToolsTestApplication.class)
.listeners(new EmbeddedServerPortFileWriter("target/server.port"))
.run(args);
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2012-2016 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;
/**
* Launches an application with DevTools.
*
* @author Andy Wilkinson
*/
public interface ApplicationLauncher {
LaunchedApplication launchApplication(JvmLauncher javaLauncher) throws Exception;
}

View File

@@ -0,0 +1,200 @@
/*
* 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();
System.out.println("Launching " + this.javaLauncher.getClass());
this.launchedApplication = this.applicationLauncher
.launchApplication(this.javaLauncher);
}
@After
public void stopApplication() throws InterruptedException {
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) {
System.out.println("Getting server port " + this.serverPortFile.length());
if (System.currentTimeMillis() > end) {
throw new IllegalStateException(String.format(
"server.port file was not written within 30 seconds. "
+ "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);
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);
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.util.ArrayList;
import java.util.List;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StringUtils;
/**
* {@link ApplicationLauncher} that launches a remote application with its classes
* available directly on the file system.
*
* @author Andy Wilkinson
*/
public class ExplodedRemoteApplicationLauncher extends RemoteApplicationLauncher {
@Override
protected String createApplicationClassPath() throws Exception {
File appDirectory = new File("target/app");
FileSystemUtils.deleteRecursively(appDirectory);
appDirectory.mkdirs();
FileSystemUtils.copyRecursively(new File("target/test-classes/com"),
new File("target/app/com"));
List<String> entries = new ArrayList<>();
entries.add("target/app");
for (File jar : new File("target/dependencies").listFiles()) {
entries.add(jar.getAbsolutePath());
}
return StringUtils.collectionToDelimitedString(entries, File.pathSeparator);
}
@Override
public String toString() {
return "exploded remote";
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
/**
* {@link ApplicationLauncher} that launches a remote application with its classes in a
* jar file.
*
* @author Andy Wilkinson
*/
public class JarFileRemoteApplicationLauncher extends RemoteApplicationLauncher {
@Override
protected String createApplicationClassPath() throws Exception {
File appDirectory = new File("target/app");
if (appDirectory.isDirectory()
&& !FileSystemUtils.deleteRecursively(appDirectory.toPath())) {
throw new IllegalStateException(
"Failed to delete '" + appDirectory.getAbsolutePath() + "'");
}
appDirectory.mkdirs();
Manifest manifest = new Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
JarOutputStream output = new JarOutputStream(
new FileOutputStream(new File(appDirectory, "app.jar")), manifest);
FileSystemUtils.copyRecursively(new File("target/test-classes/com"),
new File("target/app/com"));
addToJar(output, new File("target/app/"), new File("target/app/"));
output.close();
List<String> entries = new ArrayList<>();
entries.add("target/app/app.jar");
for (File jar : new File("target/dependencies").listFiles()) {
entries.add(jar.getAbsolutePath());
}
String classpath = StringUtils.collectionToDelimitedString(entries,
File.pathSeparator);
return classpath;
}
private void addToJar(JarOutputStream output, File root, File current)
throws IOException {
for (File file : current.listFiles()) {
if (file.isDirectory()) {
addToJar(output, root, file);
}
output.putNextEntry(new ZipEntry(
file.getAbsolutePath().substring(root.getAbsolutePath().length() + 1)
.replace("\\", "/") + (file.isDirectory() ? "/" : "")));
if (file.isFile()) {
StreamUtils.copy(new FileInputStream(file), output);
}
output.closeEntry();
}
}
@Override
public String toString() {
return "jar file remote";
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/**
* JUnit {@link TestRule} that launched a JVM and redirects its output to a test
* method-specific location.
*
* @author Andy Wilkinson
*/
class JvmLauncher implements TestRule {
private File outputDirectory;
@Override
public Statement apply(Statement base, Description description) {
this.outputDirectory = new File("target/output/"
+ description.getMethodName().replaceAll("[^A-Za-z]+", ""));
this.outputDirectory.mkdirs();
return base;
}
LaunchedJvm launch(String name, String classpath, String... args) throws IOException {
List<String> command = new ArrayList<>(Arrays
.asList(System.getProperty("java.home") + "/bin/java", "-cp", classpath));
command.addAll(Arrays.asList(args));
File standardOut = new File(this.outputDirectory, name + ".out");
File standardError = new File(this.outputDirectory, name + ".err");
Process process = new ProcessBuilder(command.toArray(new String[command.size()]))
.redirectError(standardError).redirectOutput(standardOut).start();
return new LaunchedJvm(process, standardOut, standardError);
}
static class LaunchedJvm {
private final Process process;
private final File standardOut;
private final File standardError;
LaunchedJvm(Process process, File standardOut, File standardError) {
this.process = process;
this.standardOut = standardOut;
this.standardError = standardError;
}
Process getProcess() {
return this.process;
}
File getStandardOut() {
return this.standardOut;
}
File getStandardError() {
return this.standardError;
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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;
/**
* An application launched by {@link ApplicationLauncher}.
*
* @author Andy Wilkinson
*/
class LaunchedApplication {
private final File classesDirectory;
private final File standardOut;
private final File standardError;
private final Process[] processes;
LaunchedApplication(File classesDirectory, File standardOut, File standardError,
Process... processes) {
this.classesDirectory = classesDirectory;
this.standardOut = standardOut;
this.standardError = standardError;
this.processes = processes;
}
void stop() throws InterruptedException {
for (Process process : this.processes) {
process.destroy();
process.waitFor();
}
}
File getStandardOut() {
return this.standardOut;
}
File getStandardError() {
return this.standardError;
}
File getClassesDirectory() {
return this.classesDirectory;
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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.util.ArrayList;
import java.util.List;
import org.springframework.boot.devtools.tests.JvmLauncher.LaunchedJvm;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StringUtils;
/**
* {@link ApplicationLauncher} that launches a local application with DevTools enabled.
*
* @author Andy Wilkinson
*/
public class LocalApplicationLauncher implements ApplicationLauncher {
@Override
public LaunchedApplication launchApplication(JvmLauncher jvmLauncher)
throws Exception {
LaunchedJvm jvm = jvmLauncher.launch("local", createApplicationClassPath(),
"com.example.DevToolsTestApplication", "--server.port=0");
return new LaunchedApplication(new File("target/app"), jvm.getStandardOut(),
jvm.getStandardError(), jvm.getProcess());
}
protected String createApplicationClassPath() throws Exception {
File appDirectory = new File("target/app");
FileSystemUtils.deleteRecursively(appDirectory);
appDirectory.mkdirs();
FileSystemUtils.copyRecursively(new File("target/test-classes/com"),
new File("target/app/com"));
List<String> entries = new ArrayList<>();
entries.add("target/app");
for (File jar : new File("target/dependencies").listFiles()) {
entries.add(jar.getAbsolutePath());
}
return StringUtils.collectionToDelimitedString(entries, File.pathSeparator);
}
@Override
public String toString() {
return "local";
}
}

View File

@@ -0,0 +1,107 @@
/*
* 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 org.springframework.boot.devtools.RemoteSpringApplication;
import org.springframework.boot.devtools.tests.JvmLauncher.LaunchedJvm;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StringUtils;
/**
* Base class for {@link ApplicationLauncher} implementations that use
* {@link RemoteSpringApplication}.
*
* @author Andy Wilkinson
*/
abstract class RemoteApplicationLauncher implements ApplicationLauncher {
@Override
public LaunchedApplication launchApplication(JvmLauncher javaLauncher)
throws Exception {
LaunchedJvm applicationJvm = javaLauncher.launch("app",
createApplicationClassPath(), "com.example.DevToolsTestApplication",
"--server.port=12345", "--spring.devtools.remote.secret=secret");
awaitServerPort(applicationJvm.getStandardOut());
LaunchedJvm remoteSpringApplicationJvm = javaLauncher.launch(
"remote-spring-application", createRemoteSpringApplicationClassPath(),
RemoteSpringApplication.class.getName(),
"--spring.devtools.remote.secret=secret", "http://localhost:12345");
awaitRemoteSpringApplication(remoteSpringApplicationJvm.getStandardOut());
return new LaunchedApplication(new File("target/remote"),
applicationJvm.getStandardOut(), applicationJvm.getStandardError(),
applicationJvm.getProcess(), remoteSpringApplicationJvm.getProcess());
}
protected abstract String createApplicationClassPath() throws Exception;
private String createRemoteSpringApplicationClassPath() throws Exception {
File remoteDirectory = new File("target/remote");
FileSystemUtils.deleteRecursively(remoteDirectory);
remoteDirectory.mkdirs();
FileSystemUtils.copyRecursively(new File("target/test-classes/com"),
new File("target/remote/com"));
List<String> entries = new ArrayList<>();
entries.add("target/remote");
for (File jar : new File("target/dependencies").listFiles()) {
entries.add(jar.getAbsolutePath());
}
return StringUtils.collectionToDelimitedString(entries, File.pathSeparator);
}
private int awaitServerPort(File standardOut) throws Exception {
long end = System.currentTimeMillis() + 30000;
File serverPortFile = new File("target/server.port");
while (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(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 {
long end = System.currentTimeMillis() + 30000;
while (!standardOut.exists()) {
if (System.currentTimeMillis() > end) {
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);
}
}
}

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-integration-tests</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-integration-tests-embedded-servlet-container</artifactId>
<packaging>jar</packaging>
<name>Spring Boot Embedded Servlet Container Integration Tests</name>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.samskivert</groupId>
<artifactId>jmustache</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-invoker</artifactId>
<version>3.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,72 @@
/*
* 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 com.example;
import java.io.IOException;
import java.net.URL;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.system.EmbeddedServerPortFileWriter;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
/**
* Test application for verifying an embedded container's static resource handling.
*
* @author Andy Wilkinson
*/
@SpringBootApplication
public class ResourceHandlingApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(ResourceHandlingApplication.class)
.properties("server.port:0")
.listeners(new EmbeddedServerPortFileWriter("target/server.port"))
.run(args);
}
@Bean
public ServletRegistrationBean<?> resourceServletRegistration() {
ServletRegistrationBean<?> registration = new ServletRegistrationBean<HttpServlet>(
new HttpServlet() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
URL resource = getServletContext()
.getResource(req.getQueryString());
if (resource == null) {
resp.sendError(404);
}
else {
resp.getWriter().println(resource);
resp.getWriter().flush();
}
}
});
registration.addUrlMappings("/servletContext");
return registration;
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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.context.embedded;
import java.io.File;
import java.io.FileReader;
import java.lang.ProcessBuilder.Redirect;
import java.util.ArrayList;
import java.util.List;
import org.junit.rules.ExternalResource;
import org.springframework.util.FileCopyUtils;
/**
* Base {@link ExternalResource} for launching a Spring Boot application as part of a
* JUnit test.
*
* @author Andy Wilkinson
*/
abstract class AbstractApplicationLauncher extends ExternalResource {
private final ApplicationBuilder applicationBuilder;
private Process process;
private int httpPort;
protected AbstractApplicationLauncher(ApplicationBuilder applicationBuilder) {
this.applicationBuilder = applicationBuilder;
}
@Override
protected final void before() throws Throwable {
this.process = startApplication();
}
@Override
protected final void after() {
this.process.destroy();
}
public final int getHttpPort() {
return this.httpPort;
}
protected abstract List<String> getArguments(File archive);
protected abstract File getWorkingDirectory();
protected abstract String getDescription(String packaging);
private Process startApplication() throws Exception {
File workingDirectory = getWorkingDirectory();
File serverPortFile = workingDirectory == null ? new File("target/server.port")
: new File(workingDirectory, "target/server.port");
serverPortFile.delete();
File archive = this.applicationBuilder.buildApplication();
List<String> arguments = new ArrayList<>();
arguments.add(System.getProperty("java.home") + "/bin/java");
arguments.addAll(getArguments(archive));
ProcessBuilder processBuilder = new ProcessBuilder(
arguments.toArray(new String[arguments.size()]));
processBuilder.redirectOutput(Redirect.INHERIT);
processBuilder.redirectError(Redirect.INHERIT);
if (workingDirectory != null) {
processBuilder.directory(workingDirectory);
}
Process process = processBuilder.start();
this.httpPort = awaitServerPort(process, serverPortFile);
return process;
}
private int awaitServerPort(Process process, File serverPortFile) throws Exception {
long end = System.currentTimeMillis() + 30000;
while (serverPortFile.length() == 0) {
if (System.currentTimeMillis() > end) {
throw new IllegalStateException(
"server.port file was not written within 30 seconds");
}
if (!process.isAlive()) {
throw new IllegalStateException("Application failed to launch");
}
Thread.sleep(100);
}
return Integer
.parseInt(FileCopyUtils.copyToString(new FileReader(serverPortFile)));
}
}

View File

@@ -0,0 +1,113 @@
/*
* 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.context.embedded;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.codehaus.plexus.util.StringUtils;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriTemplateHandler;
/**
* Base class for embedded servlet container integration tests.
*
* @author Andy Wilkinson
*/
public abstract class AbstractEmbeddedServletContainerIntegrationTests {
@ClassRule
public static final TemporaryFolder temporaryFolder = new TemporaryFolder();
@Rule
public final AbstractApplicationLauncher launcher;
protected final RestTemplate rest = new RestTemplate();
public static Object[] parameters(String packaging,
List<Class<? extends AbstractApplicationLauncher>> applicationLaunchers) {
List<Object> parameters = new ArrayList<>();
parameters.addAll(createParameters(packaging, "jetty", applicationLaunchers));
parameters.addAll(createParameters(packaging, "tomcat", applicationLaunchers));
parameters.addAll(createParameters(packaging, "undertow", applicationLaunchers));
return parameters.toArray(new Object[parameters.size()]);
}
private static List<Object> createParameters(String packaging, String container,
List<Class<? extends AbstractApplicationLauncher>> applicationLaunchers) {
List<Object> parameters = new ArrayList<>();
ApplicationBuilder applicationBuilder = new ApplicationBuilder(temporaryFolder,
packaging, container);
for (Class<? extends AbstractApplicationLauncher> launcherClass : applicationLaunchers) {
try {
AbstractApplicationLauncher launcher = launcherClass
.getDeclaredConstructor(ApplicationBuilder.class)
.newInstance(applicationBuilder);
String name = StringUtils.capitalise(container) + ": "
+ launcher.getDescription(packaging);
parameters.add(new Object[] { name, launcher });
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
return parameters;
}
protected AbstractEmbeddedServletContainerIntegrationTests(String name,
AbstractApplicationLauncher launcher) {
this.launcher = launcher;
this.rest.setErrorHandler(new ResponseErrorHandler() {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false;
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
}
});
this.rest.setUriTemplateHandler(new UriTemplateHandler() {
@Override
public URI expand(String uriTemplate, Object... uriVariables) {
return URI.create(
"http://localhost:" + launcher.getHttpPort() + uriTemplate);
}
@Override
public URI expand(String uriTemplate, Map<String, ?> uriVariables) {
return URI.create(
"http://localhost:" + launcher.getHttpPort() + uriTemplate);
}
});
}
}

View File

@@ -0,0 +1,135 @@
/*
* 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.context.embedded;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import com.samskivert.mustache.Mustache;
import org.apache.maven.shared.invoker.DefaultInvocationRequest;
import org.apache.maven.shared.invoker.DefaultInvoker;
import org.apache.maven.shared.invoker.InvocationRequest;
import org.apache.maven.shared.invoker.InvocationResult;
import org.apache.maven.shared.invoker.MavenInvocationException;
import org.junit.rules.TemporaryFolder;
import org.springframework.util.FileCopyUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Builds a Spring Boot application using Maven. To use this class, the {@code maven.home}
* system property must be set.
*
* @author Andy Wilkinson
*/
class ApplicationBuilder {
private final TemporaryFolder temp;
private final String packaging;
private final String container;
ApplicationBuilder(TemporaryFolder temp, String packaging, String container) {
this.temp = temp;
this.packaging = packaging;
this.container = container;
}
File buildApplication() throws Exception {
File containerFolder = new File(this.temp.getRoot(), this.container);
if (containerFolder.exists()) {
return new File(containerFolder, "app/target/app-0.0.1." + this.packaging);
}
return doBuildApplication(containerFolder);
}
private File doBuildApplication(File containerFolder)
throws IOException, FileNotFoundException, MavenInvocationException {
File resourcesJar = createResourcesJar();
File appFolder = new File(containerFolder, "app");
appFolder.mkdirs();
writePom(appFolder, resourcesJar);
copyApplicationSource(appFolder);
packageApplication(appFolder);
return new File(appFolder, "target/app-0.0.1." + this.packaging);
}
private File createResourcesJar() throws IOException, FileNotFoundException {
File resourcesJar = new File(this.temp.getRoot(), "resources.jar");
if (resourcesJar.exists()) {
return resourcesJar;
}
JarOutputStream resourcesJarStream = new JarOutputStream(
new FileOutputStream(resourcesJar));
resourcesJarStream.putNextEntry(new ZipEntry("META-INF/resources/"));
resourcesJarStream.closeEntry();
resourcesJarStream.putNextEntry(
new ZipEntry("META-INF/resources/nested-meta-inf-resource.txt"));
resourcesJarStream.write("nested".getBytes());
resourcesJarStream.closeEntry();
resourcesJarStream.close();
return resourcesJar;
}
private void writePom(File appFolder, File resourcesJar)
throws FileNotFoundException, IOException {
Map<String, Object> context = new HashMap<>();
context.put("packaging", this.packaging);
context.put("container", this.container);
context.put("bootVersion", Versions.getBootVersion());
context.put("resourcesJarPath", resourcesJar.getAbsolutePath());
FileWriter out = new FileWriter(new File(appFolder, "pom.xml"));
Mustache.compiler().escapeHTML(false)
.compile(new FileReader("src/test/resources/pom-template.xml"))
.execute(context, out);
out.close();
}
private void copyApplicationSource(File appFolder) throws IOException {
File examplePackage = new File(appFolder, "src/main/java/com/example");
examplePackage.mkdirs();
FileCopyUtils.copy(
new File("src/test/java/com/example/ResourceHandlingApplication.java"),
new File(examplePackage, "ResourceHandlingApplication.java"));
if ("war".equals(this.packaging)) {
File srcMainWebapp = new File(appFolder, "src/main/webapp");
srcMainWebapp.mkdirs();
FileCopyUtils.copy("webapp resource",
new FileWriter(new File(srcMainWebapp, "webapp-resource.txt")));
}
}
private void packageApplication(File appFolder) throws MavenInvocationException {
InvocationRequest invocation = new DefaultInvocationRequest();
invocation.setBaseDirectory(appFolder);
invocation.setGoals(Collections.singletonList("package"));
InvocationResult execute = new DefaultInvoker().execute(invocation);
assertThat(execute.getExitCode()).isEqualTo(0);
}
}

View File

@@ -0,0 +1,149 @@
/*
* 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.context.embedded;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
/**
* {@link AbstractApplicationLauncher} that launches a Spring Boot application with a
* classpath similar to that used when run with Maven or Gradle.
*
* @author Andy Wilkinson
*/
class BootRunApplicationLauncher extends AbstractApplicationLauncher {
private final File exploded = new File("target/run");
BootRunApplicationLauncher(ApplicationBuilder applicationBuilder) {
super(applicationBuilder);
}
@Override
protected List<String> getArguments(File archive) {
try {
explodeArchive(archive);
deleteLauncherClasses();
File targetClasses = populateTargetClasses(archive);
File dependencies = populateDependencies(archive);
if (archive.getName().endsWith(".war")) {
populateSrcMainWebapp();
}
List<String> classpath = new ArrayList<>();
classpath.add(targetClasses.getAbsolutePath());
for (File dependency : dependencies.listFiles()) {
classpath.add(dependency.getAbsolutePath());
}
return Arrays.asList("-cp",
StringUtils.collectionToDelimitedString(classpath,
File.pathSeparator),
"com.example.ResourceHandlingApplication");
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
private void deleteLauncherClasses() {
FileSystemUtils.deleteRecursively(new File(this.exploded, "org"));
}
private File populateTargetClasses(File archive) throws IOException {
File targetClasses = new File(this.exploded, "target/classes");
targetClasses.mkdirs();
File source = new File(this.exploded, getClassesPath(archive));
FileSystemUtils.copyRecursively(source, targetClasses);
FileSystemUtils.deleteRecursively(source);
return targetClasses;
}
private File populateDependencies(File archive) throws IOException {
File dependencies = new File(this.exploded, "dependencies");
dependencies.mkdirs();
List<String> libPaths = getLibPaths(archive);
for (String libPath : libPaths) {
File libDirectory = new File(this.exploded, libPath);
for (File jar : libDirectory.listFiles()) {
FileCopyUtils.copy(jar, new File(dependencies, jar.getName()));
}
FileSystemUtils.deleteRecursively(libDirectory);
}
return dependencies;
}
private void populateSrcMainWebapp() throws IOException {
File srcMainWebapp = new File(this.exploded, "src/main/webapp");
srcMainWebapp.mkdirs();
File source = new File(this.exploded, "webapp-resource.txt");
FileCopyUtils.copy(source, new File(srcMainWebapp, "webapp-resource.txt"));
source.delete();
}
private String getClassesPath(File archive) {
return archive.getName().endsWith(".jar") ? "BOOT-INF/classes"
: "WEB-INF/classes";
}
private List<String> getLibPaths(File archive) {
return archive.getName().endsWith(".jar")
? Collections.singletonList("BOOT-INF/lib")
: Arrays.asList("WEB-INF/lib", "WEB-INF/lib-provided");
}
private void explodeArchive(File archive) throws IOException {
FileSystemUtils.deleteRecursively(this.exploded);
JarFile jarFile = new JarFile(archive);
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
File extracted = new File(this.exploded, jarEntry.getName());
if (jarEntry.isDirectory()) {
extracted.mkdirs();
}
else {
FileOutputStream extractedOutputStream = new FileOutputStream(extracted);
StreamUtils.copy(jarFile.getInputStream(jarEntry), extractedOutputStream);
extractedOutputStream.close();
}
}
jarFile.close();
}
@Override
protected File getWorkingDirectory() {
return this.exploded;
}
@Override
protected String getDescription(String packaging) {
return "build system run " + packaging + " project";
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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.context.embedded;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for Spring Boot's embedded servlet container support when developing
* a jar application.
*
* @author Andy Wilkinson
*/
@RunWith(Parameterized.class)
public class EmbeddedServletContainerJarDevelopmentIntegrationTests
extends AbstractEmbeddedServletContainerIntegrationTests {
@Parameters(name = "{0}")
public static Object[] parameters() {
return AbstractEmbeddedServletContainerIntegrationTests.parameters("jar", Arrays
.asList(BootRunApplicationLauncher.class, IdeApplicationLauncher.class));
}
public EmbeddedServletContainerJarDevelopmentIntegrationTests(String name,
AbstractApplicationLauncher launcher) {
super(name, launcher);
}
@Test
public void metaInfResourceFromDependencyIsAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest
.getForEntity("/nested-meta-inf-resource.txt", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void metaInfResourceFromDependencyIsAvailableViaServletContext()
throws Exception {
ResponseEntity<String> entity = this.rest.getForEntity(
"/servletContext?/nested-meta-inf-resource.txt", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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.context.embedded;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for Spring Boot's embedded servlet container support using jar
* packaging.
*
* @author Andy Wilkinson
*/
@RunWith(Parameterized.class)
public class EmbeddedServletContainerJarPackagingIntegrationTests
extends AbstractEmbeddedServletContainerIntegrationTests {
@Parameters(name = "{0}")
public static Object[] parameters() {
return AbstractEmbeddedServletContainerIntegrationTests.parameters("jar",
Arrays.asList(PackagedApplicationLauncher.class,
ExplodedApplicationLauncher.class));
}
public EmbeddedServletContainerJarPackagingIntegrationTests(String name,
AbstractApplicationLauncher launcher) {
super(name, launcher);
}
@Test
public void nestedMetaInfResourceIsAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest
.getForEntity("/nested-meta-inf-resource.txt", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void nestedMetaInfResourceIsAvailableViaServletContext() throws Exception {
ResponseEntity<String> entity = this.rest.getForEntity(
"/servletContext?/nested-meta-inf-resource.txt", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void nestedJarIsNotAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest
.getForEntity("/BOOT-INF/lib/resources-1.0.jar", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
@Test
public void applicationClassesAreNotAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest.getForEntity(
"/BOOT-INF/classes/com/example/ResourceHandlingApplication.class",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
@Test
public void launcherIsNotAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest.getForEntity(
"/org/springframework/boot/loader/Launcher.class", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.context.embedded;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for Spring Boot's embedded servlet container support when developing
* a war application.
*
* @author Andy Wilkinson
*/
@RunWith(Parameterized.class)
public class EmbeddedServletContainerWarDevelopmentIntegrationTests
extends AbstractEmbeddedServletContainerIntegrationTests {
@Parameters(name = "{0}")
public static Object[] parameters() {
return AbstractEmbeddedServletContainerIntegrationTests.parameters("war", Arrays
.asList(BootRunApplicationLauncher.class, IdeApplicationLauncher.class));
}
public EmbeddedServletContainerWarDevelopmentIntegrationTests(String name,
AbstractApplicationLauncher launcher) {
super(name, launcher);
}
@Test
public void metaInfResourceFromDependencyIsAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest
.getForEntity("/nested-meta-inf-resource.txt", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void metaInfResourceFromDependencyIsAvailableViaServletContext()
throws Exception {
ResponseEntity<String> entity = this.rest.getForEntity(
"/servletContext?/nested-meta-inf-resource.txt", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void webappResourcesAreAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest.getForEntity("/webapp-resource.txt",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
}

View File

@@ -0,0 +1,100 @@
/*
* 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.context.embedded;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for Spring Boot's embedded servlet container support using war
* packaging.
*
* @author Andy Wilkinson
*/
@RunWith(Parameterized.class)
public class EmbeddedServletContainerWarPackagingIntegrationTests
extends AbstractEmbeddedServletContainerIntegrationTests {
@Parameters(name = "{0}")
public static Object[] parameters() {
return AbstractEmbeddedServletContainerIntegrationTests.parameters("war",
Arrays.asList(PackagedApplicationLauncher.class,
ExplodedApplicationLauncher.class));
}
public EmbeddedServletContainerWarPackagingIntegrationTests(String name,
AbstractApplicationLauncher launcher) {
super(name, launcher);
}
@Test
public void nestedMetaInfResourceIsAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest
.getForEntity("/nested-meta-inf-resource.txt", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void nestedMetaInfResourceIsAvailableViaServletContext() throws Exception {
ResponseEntity<String> entity = this.rest.getForEntity(
"/servletContext?/nested-meta-inf-resource.txt", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void nestedJarIsNotAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest
.getForEntity("/WEB-INF/lib/resources-1.0.jar", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
@Test
public void applicationClassesAreNotAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest.getForEntity(
"/WEB-INF/classes/com/example/ResourceHandlingApplication.class",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
@Test
public void webappResourcesAreAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest.getForEntity("/webapp-resource.txt",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void loaderClassesAreNotAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest.getForEntity(
"/org/springframework/boot/loader/Launcher.class", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
entity = this.rest.getForEntity(
"/org/springframework/../springframework/boot/loader/Launcher.class",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
}

View File

@@ -0,0 +1,88 @@
/*
* 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.context.embedded;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StreamUtils;
/**
* {@link AbstractApplicationLauncher} that launches a Spring Boot application using
* {@code JarLauncher} or {@code WarLauncher} and an exploded archive.
*
* @author Andy Wilkinson
*/
class ExplodedApplicationLauncher extends AbstractApplicationLauncher {
private final File exploded = new File("target/exploded");
ExplodedApplicationLauncher(ApplicationBuilder applicationBuilder) {
super(applicationBuilder);
}
@Override
protected File getWorkingDirectory() {
return this.exploded;
}
@Override
protected String getDescription(String packaging) {
return "exploded " + packaging;
}
@Override
protected List<String> getArguments(File archive) {
String mainClass = archive.getName().endsWith(".war")
? "org.springframework.boot.loader.WarLauncher"
: "org.springframework.boot.loader.JarLauncher";
try {
explodeArchive(archive);
return Arrays.asList("-cp", this.exploded.getAbsolutePath(), mainClass);
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
private void explodeArchive(File archive) throws IOException {
FileSystemUtils.deleteRecursively(this.exploded);
JarFile jarFile = new JarFile(archive);
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
File extracted = new File(this.exploded, jarEntry.getName());
if (jarEntry.isDirectory()) {
extracted.mkdirs();
}
else {
FileOutputStream extractedOutputStream = new FileOutputStream(extracted);
StreamUtils.copy(jarFile.getInputStream(jarEntry), extractedOutputStream);
extractedOutputStream.close();
}
}
jarFile.close();
}
}

View File

@@ -0,0 +1,160 @@
/*
* 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.context.embedded;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
/**
* {@link AbstractApplicationLauncher} that launches a Spring Boot application with a
* classpath similar to that used when run in an IDE.
*
* @author Andy Wilkinson
*/
class IdeApplicationLauncher extends AbstractApplicationLauncher {
private final File exploded = new File("target/ide");
IdeApplicationLauncher(ApplicationBuilder applicationBuilder) {
super(applicationBuilder);
}
@Override
protected File getWorkingDirectory() {
return this.exploded;
}
@Override
protected String getDescription(String packaging) {
return "IDE run " + packaging + " project";
}
@Override
protected List<String> getArguments(File archive) {
try {
explodeArchive(archive, this.exploded);
deleteLauncherClasses();
File targetClasses = populateTargetClasses(archive);
File dependencies = populateDependencies(archive);
File resourcesProject = explodedResourcesProject(dependencies);
if (archive.getName().endsWith(".war")) {
populateSrcMainWebapp();
}
List<String> classpath = new ArrayList<>();
classpath.add(targetClasses.getAbsolutePath());
for (File dependency : dependencies.listFiles()) {
classpath.add(dependency.getAbsolutePath());
}
classpath.add(resourcesProject.getAbsolutePath());
return Arrays.asList("-cp",
StringUtils.collectionToDelimitedString(classpath,
File.pathSeparator),
"com.example.ResourceHandlingApplication");
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
private File populateTargetClasses(File archive) throws IOException {
File targetClasses = new File(this.exploded, "target/classes");
targetClasses.mkdirs();
File source = new File(this.exploded, getClassesPath(archive));
FileSystemUtils.copyRecursively(source, targetClasses);
FileSystemUtils.deleteRecursively(source);
return targetClasses;
}
private File populateDependencies(File archive) throws IOException {
File dependencies = new File(this.exploded, "dependencies");
dependencies.mkdirs();
List<String> libPaths = getLibPaths(archive);
for (String libPath : libPaths) {
File libDirectory = new File(this.exploded, libPath);
for (File jar : libDirectory.listFiles()) {
FileCopyUtils.copy(jar, new File(dependencies, jar.getName()));
}
FileSystemUtils.deleteRecursively(libDirectory);
}
return dependencies;
}
private File explodedResourcesProject(File dependencies) throws IOException {
File resourcesProject = new File(this.exploded,
"resources-project/target/classes");
File resourcesJar = new File(dependencies, "resources-1.0.jar");
explodeArchive(resourcesJar, resourcesProject);
resourcesJar.delete();
return resourcesProject;
}
private void populateSrcMainWebapp() throws IOException {
File srcMainWebapp = new File(this.exploded, "src/main/webapp");
srcMainWebapp.mkdirs();
File source = new File(this.exploded, "webapp-resource.txt");
FileCopyUtils.copy(source, new File(srcMainWebapp, "webapp-resource.txt"));
source.delete();
}
private void deleteLauncherClasses() {
FileSystemUtils.deleteRecursively(new File(this.exploded, "org"));
}
private String getClassesPath(File archive) {
return archive.getName().endsWith(".jar") ? "BOOT-INF/classes"
: "WEB-INF/classes";
}
private List<String> getLibPaths(File archive) {
return archive.getName().endsWith(".jar")
? Collections.singletonList("BOOT-INF/lib")
: Arrays.asList("WEB-INF/lib", "WEB-INF/lib-provided");
}
private void explodeArchive(File archive, File destination) throws IOException {
FileSystemUtils.deleteRecursively(destination);
JarFile jarFile = new JarFile(archive);
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
File extracted = new File(destination, jarEntry.getName());
if (jarEntry.isDirectory()) {
extracted.mkdirs();
}
else {
FileOutputStream extractedOutputStream = new FileOutputStream(extracted);
StreamUtils.copy(jarFile.getInputStream(jarEntry), extractedOutputStream);
extractedOutputStream.close();
}
}
jarFile.close();
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.context.embedded;
import java.io.File;
import java.util.Arrays;
import java.util.List;
/**
* {@link AbstractApplicationLauncher} that launches a packaged Spring Boot application
* using {@code java -jar}.
*
* @author Andy Wilkinson
*/
class PackagedApplicationLauncher extends AbstractApplicationLauncher {
PackagedApplicationLauncher(ApplicationBuilder applicationBuilder) {
super(applicationBuilder);
}
@Override
protected File getWorkingDirectory() {
return null;
}
@Override
protected String getDescription(String packaging) {
return "packaged " + packaging;
}
@Override
protected List<String> getArguments(File archive) {
return Arrays.asList("-jar", archive.getAbsolutePath());
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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.context.embedded;
import java.io.FileReader;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import org.xml.sax.InputSource;
/**
* Provides access to dependency versions by querying the project's pom.
*
* @author Andy Wilkinson
*/
final class Versions {
private Versions() {
}
public static String getBootVersion() {
return evaluateExpression(
"/*[local-name()='project']/*[local-name()='parent']/*[local-name()='version']"
+ "/text()");
}
private static String evaluateExpression(String expression) {
try {
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
XPathExpression expr = xpath.compile(expression);
String version = expr.evaluate(new InputSource(new FileReader("pom.xml")));
return version;
}
catch (Exception ex) {
throw new IllegalStateException("Failed to evaluate expression", ex);
}
}
}

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>{{bootVersion}}</version>
<relativePath />
</parent>
<groupId>com.example</groupId>
<artifactId>app</artifactId>
<version>0.0.1</version>
<packaging>{{packaging}}</packaging>
<properties>
<resourcesJarPath>{{resourcesJarPath}}</resourcesJarPath>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-{{container}}</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>resources</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${resourcesJarPath}</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<url>https://repo.spring.io/libs-snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</project>

View File

@@ -0,0 +1,77 @@
= Spring Boot Launch Script Tests
This module contains integration tests for the default launch script that is used
to make a jar file fully executable on Linux. The tests use Docker to verify the
functionality in a variety of Linux distributions.
== Setting up Docker
The setup that's required varies depending on your operating system.
=== Docker on OS X
The latest version of Docker runs as a native Mac application but isn't supported by
docker-java. This means that you should use Docker Toolbox. See the
https://docs.docker.com/engine/installation/mac/[OS X installation instructions] for
details.
=== Docker on Linux
Install Docker as appropriate for your Linux distribution. See the
https://docs.docker.com/engine/installation/[Linux installation instructions] for more
information.
Next, add your user to the `docker` group. For example:
----
$ sudo usermod -a -G docker awilkinson
----
You may need to log out and back in again for this change to take affect and for your
user to be able to connect to the daemon.
== Preparing to run the tests
Before running the tests, you must prepare your environment according to your operating
system.
=== Preparation on OS X
The tests must be run in an environment where various environment variables including
`DOCKER_HOST` and `DOCKER_CERT_PATH` have been set:
----
$ eval $(docker-machine env default)
----
=== Preparation on Linux
Docker Daemon's default configuration on Linux uses a Unix socket for communication.
However, Docker's Java client uses HTTP by default. Docker Java's client can be configured
to use the Unix socket via the `DOCKER_URL` environment variable:
----
$ export DOCKER_URL=unix:///var/run/docker.sock
----
== Running the tests
You're now ready to run the tests. Assuming that you're in the same directory as this
README, the tests can be launched as follows:
----
$ mvn -Pdocker clean verify
----
The first time the tests are run, Docker will create the container images that are used to
run the tests. This can take several minutes, particularly if you have a slow network
connection. Subsequent runs will be faster as the images are cached locally. You can run
`docker images` to see a list of the cached images. Images created by these tests will be
tagged with `spring-boot-it` prefix to easily distinguish them.
== Cleaning up
If you want to reclaim the disk space used by the cached images (at the expense of having
to wait for them to be downloaded and rebuilt the next time you run the tests), you can
use `docker images` to list the images and `docker rmi <image>` to delete them (look for
`spring-boot-it` tag). See `docker rmi --help` for further details.

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-integration-tests</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-launch-script-tests</artifactId>
<packaging>jar</packaging>
<name>Spring Boot Launch Script Integration Tests</name>
<description>Spring Boot Launch Script Integration Tests</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
<jersey.version>2.11</jersey.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java</artifactId>
<version>2.2.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>docker</id>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2012-2016 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.launchscript;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class LaunchScriptTestApplication {
public static void main(String[] args) {
SpringApplication.run(LaunchScriptTestApplication.class, args);
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2012-2016 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.launchscript;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LaunchVerificationController {
@RequestMapping("/")
public String verifyLaunch() {
return "Launched";
}
}

View File

@@ -0,0 +1,466 @@
/*
* 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.launchscript;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.DockerClientException;
import com.github.dockerjava.api.command.DockerCmd;
import com.github.dockerjava.api.model.BuildResponseItem;
import com.github.dockerjava.api.model.Frame;
import com.github.dockerjava.core.CompressArchiveUtil;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.command.AttachContainerResultCallback;
import com.github.dockerjava.core.command.BuildImageResultCallback;
import com.github.dockerjava.jaxrs.AbstrSyncDockerCmdExec;
import com.github.dockerjava.jaxrs.DockerCmdExecFactoryImpl;
import org.assertj.core.api.Condition;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.boot.ansi.AnsiColor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assume.assumeThat;
/**
* Integration tests for Spring Boot's launch script on OSs that use SysVinit.
*
* @author Andy Wilkinson
* @author Ali Shahbour
*/
@RunWith(Parameterized.class)
public class SysVinitLaunchScriptIT {
private final SpringBootDockerCmdExecFactory commandExecFactory = new SpringBootDockerCmdExecFactory();
private static final char ESC = 27;
private final String os;
private final String version;
@Parameters(name = "{0} {1}")
public static List<Object[]> parameters() {
List<Object[]> parameters = new ArrayList<>();
for (File os : new File("src/test/resources/conf").listFiles()) {
for (File version : os.listFiles()) {
parameters.add(new Object[] { os.getName(), version.getName() });
}
}
return parameters;
}
public SysVinitLaunchScriptIT(String os, String version) {
this.os = os;
this.version = version;
}
@Test
public void statusWhenStopped() throws Exception {
String output = doTest("status-when-stopped.sh");
assertThat(output).contains("Status: 3");
assertThat(output).has(coloredString(AnsiColor.RED, "Not running"));
}
@Test
public void statusWhenStarted() throws Exception {
String output = doTest("status-when-started.sh");
assertThat(output).contains("Status: 0");
assertThat(output).has(
coloredString(AnsiColor.GREEN, "Started [" + extractPid(output) + "]"));
}
@Test
public void statusWhenKilled() throws Exception {
String output = doTest("status-when-killed.sh");
assertThat(output).contains("Status: 1");
assertThat(output).has(coloredString(AnsiColor.RED,
"Not running (process " + extractPid(output) + " not found)"));
}
@Test
public void stopWhenStopped() throws Exception {
String output = doTest("stop-when-stopped.sh");
assertThat(output).contains("Status: 0");
assertThat(output)
.has(coloredString(AnsiColor.YELLOW, "Not running (pidfile not found)"));
}
@Test
public void forceStopWhenStopped() throws Exception {
String output = doTest("force-stop-when-stopped.sh");
assertThat(output).contains("Status: 0");
assertThat(output)
.has(coloredString(AnsiColor.YELLOW, "Not running (pidfile not found)"));
}
@Test
public void startWhenStarted() throws Exception {
String output = doTest("start-when-started.sh");
assertThat(output).contains("Status: 0");
assertThat(output).has(coloredString(AnsiColor.YELLOW,
"Already running [" + extractPid(output) + "]"));
}
@Test
public void restartWhenStopped() throws Exception {
String output = doTest("restart-when-stopped.sh");
assertThat(output).contains("Status: 0");
assertThat(output)
.has(coloredString(AnsiColor.YELLOW, "Not running (pidfile not found)"));
assertThat(output).has(
coloredString(AnsiColor.GREEN, "Started [" + extractPid(output) + "]"));
}
@Test
public void restartWhenStarted() throws Exception {
String output = doTest("restart-when-started.sh");
assertThat(output).contains("Status: 0");
assertThat(output).has(coloredString(AnsiColor.GREEN,
"Started [" + extract("PID1", output) + "]"));
assertThat(output).has(coloredString(AnsiColor.GREEN,
"Stopped [" + extract("PID1", output) + "]"));
assertThat(output).has(coloredString(AnsiColor.GREEN,
"Started [" + extract("PID2", output) + "]"));
}
@Test
public void startWhenStopped() throws Exception {
String output = doTest("start-when-stopped.sh");
assertThat(output).contains("Status: 0");
assertThat(output).has(
coloredString(AnsiColor.GREEN, "Started [" + extractPid(output) + "]"));
}
@Test
public void basicLaunch() throws Exception {
doLaunch("basic-launch.sh");
}
@Test
public void launchWithSingleCommandLineArgument() throws Exception {
doLaunch("launch-with-single-command-line-argument.sh");
}
@Test
public void launchWithMultipleCommandLineArguments() throws Exception {
doLaunch("launch-with-multiple-command-line-arguments.sh");
}
@Test
public void launchWithSingleRunArg() throws Exception {
doLaunch("launch-with-single-run-arg.sh");
}
@Test
public void launchWithMultipleRunArgs() throws Exception {
doLaunch("launch-with-multiple-run-args.sh");
}
@Test
public void launchWithSingleJavaOpt() throws Exception {
doLaunch("launch-with-single-java-opt.sh");
}
@Test
public void launchWithDoubleLinkSingleJavaOpt() throws Exception {
doLaunch("launch-with-double-link-single-java-opt.sh");
}
@Test
public void launchWithMultipleJavaOpts() throws Exception {
doLaunch("launch-with-multiple-java-opts.sh");
}
@Test
public void launchWithUseOfStartStopDaemonDisabled() throws Exception {
// CentOS doesn't have start-stop-daemon
assumeThat(this.os, is(not("CentOS")));
doLaunch("launch-with-use-of-start-stop-daemon-disabled.sh");
}
@Test
public void launchWithRelativePidFolder() throws Exception {
String output = doTest("launch-with-relative-pid-folder.sh");
assertThat(output).has(
coloredString(AnsiColor.GREEN, "Started [" + extractPid(output) + "]"));
assertThat(output).has(
coloredString(AnsiColor.GREEN, "Running [" + extractPid(output) + "]"));
assertThat(output).has(
coloredString(AnsiColor.GREEN, "Stopped [" + extractPid(output) + "]"));
}
@Test
public void launchWithRelativeLogFolder() throws Exception {
String output = doTest("launch-with-relative-log-folder.sh");
assertThat(output).contains("Log written");
}
private void doLaunch(String script) throws Exception {
assertThat(doTest(script)).contains("Launched");
}
private String doTest(String script) throws Exception {
DockerClient docker = createClient();
String imageId = buildImage(docker);
String container = createContainer(docker, imageId, script);
try {
copyFilesToContainer(docker, container, script);
docker.startContainerCmd(container).exec();
StringBuilder output = new StringBuilder();
AttachContainerResultCallback resultCallback = docker
.attachContainerCmd(container).withStdOut(true).withStdErr(true)
.withFollowStream(true).withLogs(true)
.exec(new AttachContainerResultCallback() {
@Override
public void onNext(Frame item) {
output.append(new String(item.getPayload()));
super.onNext(item);
}
});
resultCallback.awaitCompletion(60, TimeUnit.SECONDS).close();
docker.waitContainerCmd(container).exec();
return output.toString();
}
finally {
try {
docker.removeContainerCmd(container).exec();
}
catch (Exception ex) {
// Continue
}
}
}
private DockerClient createClient() {
DockerClientConfig config = DockerClientConfig.createDefaultConfigBuilder()
.withVersion("1.19").build();
DockerClient docker = DockerClientBuilder.getInstance(config)
.withDockerCmdExecFactory(this.commandExecFactory).build();
return docker;
}
private String buildImage(DockerClient docker) {
String dockerfile = "src/test/resources/conf/" + this.os + "/" + this.version
+ "/Dockerfile";
String tag = "spring-boot-it/" + this.os.toLowerCase() + ":" + this.version;
BuildImageResultCallback resultCallback = new BuildImageResultCallback() {
private List<BuildResponseItem> items = new ArrayList<>();
@Override
public void onNext(BuildResponseItem item) {
super.onNext(item);
this.items.add(item);
}
@Override
public String awaitImageId() {
try {
awaitCompletion();
}
catch (InterruptedException ex) {
throw new DockerClientException(
"Interrupted while waiting for image id", ex);
}
return getImageId();
}
@SuppressWarnings("deprecation")
private String getImageId() {
if (this.items.isEmpty()) {
throw new DockerClientException("Could not build image");
}
String imageId = extractImageId();
if (imageId == null) {
throw new DockerClientException("Could not build image: "
+ this.items.get(this.items.size() - 1).getError());
}
return imageId;
}
private String extractImageId() {
Collections.reverse(this.items);
for (BuildResponseItem item : this.items) {
if (item.isErrorIndicated() || item.getStream() == null) {
return null;
}
if (item.getStream().contains("Successfully built")) {
return item.getStream().replace("Successfully built", "").trim();
}
}
return null;
}
};
docker.buildImageCmd(new File(dockerfile)).withTag(tag).exec(resultCallback);
String imageId = resultCallback.awaitImageId();
return imageId;
}
private String createContainer(DockerClient docker, String imageId,
String testScript) {
return docker.createContainerCmd(imageId).withTty(false).withCmd("/bin/bash",
"-c", "chmod +x " + testScript + " && ./" + testScript).exec().getId();
}
private void copyFilesToContainer(DockerClient docker, final String container,
String script) {
copyToContainer(docker, container, findApplication());
copyToContainer(docker, container,
new File("src/test/resources/scripts/test-functions.sh"));
copyToContainer(docker, container,
new File("src/test/resources/scripts/" + script));
}
private void copyToContainer(DockerClient docker, final String container,
final File file) {
this.commandExecFactory.createCopyToContainerCmdExec()
.exec(new CopyToContainerCmd(container, file));
}
private File findApplication() {
File targetDir = new File("target");
for (File file : targetDir.listFiles()) {
if (file.getName().startsWith("spring-boot-launch-script-tests")
&& file.getName().endsWith(".jar")
&& !file.getName().endsWith("-sources.jar")) {
return file;
}
}
throw new IllegalStateException(
"Could not find test application in target directory. Have you built it (mvn package)?");
}
private Condition<String> coloredString(AnsiColor color, String string) {
String colorString = ESC + "[0;" + color + "m" + string + ESC + "[0m";
return new Condition<String>() {
@Override
public boolean matches(String value) {
return containsString(colorString).matches(value);
}
};
}
private String extractPid(String output) {
return extract("PID", output);
}
private String extract(String label, String output) {
Pattern pattern = Pattern.compile(".*" + label + ": ([0-9]+).*", Pattern.DOTALL);
java.util.regex.Matcher matcher = pattern.matcher(output);
if (matcher.matches()) {
return matcher.group(1);
}
throw new IllegalArgumentException(
"Failed to extract " + label + " from output: " + output);
}
private static final class CopyToContainerCmdExec
extends AbstrSyncDockerCmdExec<CopyToContainerCmd, Void> {
private CopyToContainerCmdExec(WebTarget baseResource,
DockerClientConfig dockerClientConfig) {
super(baseResource, dockerClientConfig);
}
@Override
protected Void execute(CopyToContainerCmd command) {
try {
InputStream streamToUpload = new FileInputStream(CompressArchiveUtil
.archiveTARFiles(command.getFile().getParentFile(),
Arrays.asList(command.getFile()),
command.getFile().getName()));
WebTarget webResource = getBaseResource().path("/containers/{id}/archive")
.resolveTemplate("id", command.getContainer());
webResource.queryParam("path", ".")
.queryParam("noOverwriteDirNonDir", false).request()
.put(Entity.entity(streamToUpload, "application/x-tar")).close();
return null;
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
private static final class CopyToContainerCmd implements DockerCmd<Void> {
private final String container;
private final File file;
private CopyToContainerCmd(String container, File file) {
this.container = container;
this.file = file;
}
public String getContainer() {
return this.container;
}
public File getFile() {
return this.file;
}
@Override
public void close() {
}
}
private static final class SpringBootDockerCmdExecFactory
extends DockerCmdExecFactoryImpl {
private SpringBootDockerCmdExecFactory() {
withClientRequestFilters((requestContext) ->
// Workaround for https://go-review.googlesource.com/#/c/3821/
requestContext.getHeaders().add("Connection", "close"));
}
private CopyToContainerCmdExec createCopyToContainerCmdExec() {
return new CopyToContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
}
}

View File

@@ -0,0 +1,9 @@
# CentOS 6.9 from 02/06/2017
FROM centos@sha256:a23bced61701af9a0a758e94229676d9f09996a3ff0f3d26955b06bac8c282e0
RUN yum install -y wget && \
yum install -y system-config-services && \
yum install -y curl && \
wget --output-document jdk.rpm \
http://cdn.azul.com/zulu/bin/zulu8.21.0.1-jdk8.0.131-linux.x86_64.rpm && \
yum --nogpg localinstall -y jdk.rpm && \
rm -f jdk.rpm

View File

@@ -0,0 +1,9 @@
FROM ubuntu:trusty-20160914
RUN apt-get update && \
apt-get install -y software-properties-common && \
add-apt-repository ppa:webupd8team/java -y && \
apt-get update && \
echo oracle-java7-installer shared/accepted-oracle-license-v1-1 select true | /usr/bin/debconf-set-selections && \
apt-get install -y oracle-java8-installer && \
apt-get install -y curl && \
apt-get clean

View File

@@ -0,0 +1,9 @@
FROM ubuntu:xenial-20160914
RUN apt-get update && \
apt-get install -y software-properties-common && \
add-apt-repository ppa:webupd8team/java -y && \
apt-get update && \
echo oracle-java7-installer shared/accepted-oracle-license-v1-1 select true | /usr/bin/debconf-set-selections && \
apt-get install -y oracle-java8-installer && \
apt-get install -y curl && \
apt-get clean

View File

@@ -0,0 +1,15 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.github.dockerjava.core.command" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@@ -0,0 +1,5 @@
source ./test-functions.sh
install_service
start_service
await_app
curl -s http://127.0.0.1:8080/

View File

@@ -0,0 +1,4 @@
source ./test-functions.sh
install_service
force_stop_service
echo "Status: $?"

View File

@@ -0,0 +1,6 @@
source ./test-functions.sh
install_double_link_service
echo 'JAVA_OPTS=-Dserver.port=8081' > /test-service/spring-boot-app.conf
start_service
await_app http://127.0.0.1:8081/
curl -s http://127.0.0.1:8081/

View File

@@ -0,0 +1,5 @@
source ./test-functions.sh
install_service
start_service --server.port=8081 --server.servlet.context-path=/test
await_app http://127.0.0.1:8081/test/
curl -s http://127.0.0.1:8081/test/

View File

@@ -0,0 +1,6 @@
source ./test-functions.sh
install_service
echo 'JAVA_OPTS="-Dserver.port=8081 -Dserver.servlet.context-path=/test"' > /test-service/spring-boot-app.conf
start_service
await_app http://127.0.0.1:8081/test/
curl -s http://127.0.0.1:8081/test/

View File

@@ -0,0 +1,6 @@
source ./test-functions.sh
install_service
echo 'RUN_ARGS="--server.port=8081 --server.servlet.context-path=/test"' > /test-service/spring-boot-app.conf
start_service
await_app http://127.0.0.1:8081/test/
curl -s http://127.0.0.1:8081/test/

View File

@@ -0,0 +1,8 @@
source ./test-functions.sh
mkdir ./pid
install_service
echo 'LOG_FOLDER=log' > /test-service/spring-boot-app.conf
mkdir -p /test-service/log
start_service
await_app
[[ -s /test-service/log/spring-boot-app.log ]] && echo "Log written"

View File

@@ -0,0 +1,10 @@
source ./test-functions.sh
install_service
mkdir /test-service/pid
echo 'PID_FOLDER=pid' > /test-service/spring-boot-app.conf
start_service
echo "PID: $(cat /test-service/pid/spring-boot-app/spring-boot-app.pid)"
await_app
curl -s http://127.0.0.1:8080/
status_service
stop_service

View File

@@ -0,0 +1,5 @@
source ./test-functions.sh
install_service
start_service --server.port=8081
await_app http://127.0.0.1:8081/
curl -s http://127.0.0.1:8081/

View File

@@ -0,0 +1,6 @@
source ./test-functions.sh
install_service
echo 'JAVA_OPTS=-Dserver.port=8081' > /test-service/spring-boot-app.conf
start_service
await_app http://127.0.0.1:8081/
curl -s http://127.0.0.1:8081/

View File

@@ -0,0 +1,6 @@
source ./test-functions.sh
install_service
echo 'RUN_ARGS=--server.port=8081' > /test-service/spring-boot-app.conf
start_service
await_app http://127.0.0.1:8081/
curl -s http://127.0.0.1:8081/

View File

@@ -0,0 +1,7 @@
source ./test-functions.sh
chmod -x $(type -p start-stop-daemon)
install_service
echo 'USE_START_STOP_DAEMON=false' > /test-service/spring-boot-app.conf
start_service
await_app
curl -s http://127.0.0.1:8080/

View File

@@ -0,0 +1,7 @@
source ./test-functions.sh
install_service
start_service
echo "PID1: $(cat /var/run/spring-boot-app/spring-boot-app.pid)"
restart_service
echo "Status: $?"
echo "PID2: $(cat /var/run/spring-boot-app/spring-boot-app.pid)"

View File

@@ -0,0 +1,5 @@
source ./test-functions.sh
install_service
restart_service
echo "Status: $?"
echo "PID: $(cat /var/run/spring-boot-app/spring-boot-app.pid)"

View File

@@ -0,0 +1,6 @@
source ./test-functions.sh
install_service
start_service
echo "PID: $(cat /var/run/spring-boot-app/spring-boot-app.pid)"
start_service
echo "Status: $?"

View File

@@ -0,0 +1,5 @@
source ./test-functions.sh
install_service
start_service
echo "Status: $?"
echo "PID: $(cat /var/run/spring-boot-app/spring-boot-app.pid)"

View File

@@ -0,0 +1,8 @@
source ./test-functions.sh
install_service
start_service
pid=$(cat /var/run/spring-boot-app/spring-boot-app.pid)
echo "PID: $pid"
kill -9 $pid
status_service
echo "Status: $?"

View File

@@ -0,0 +1,6 @@
source ./test-functions.sh
install_service
start_service
status_service
echo "Status: $?"
echo "PID: $(cat /var/run/spring-boot-app/spring-boot-app.pid)"

View File

@@ -0,0 +1,4 @@
source ./test-functions.sh
install_service
status_service
echo "Status: $?"

View File

@@ -0,0 +1,4 @@
source ./test-functions.sh
install_service
stop_service
echo "Status: $?"

View File

@@ -0,0 +1,53 @@
install_service() {
mkdir /test-service
mv /spring-boot-launch-script-tests-*.jar /test-service/spring-boot-app.jar
chmod +x /test-service/spring-boot-app.jar
ln -s /test-service/spring-boot-app.jar /etc/init.d/spring-boot-app
}
install_double_link_service() {
mkdir /test-service
mv /spring-boot-launch-script-tests-*.jar /test-service/
chmod +x /test-service/spring-boot-launch-script-tests-*.jar
ln -s /test-service/spring-boot-launch-script-tests-*.jar /test-service/spring-boot-app.jar
ln -s /test-service/spring-boot-app.jar /etc/init.d/spring-boot-app
}
start_service() {
service spring-boot-app start $@
}
restart_service() {
service spring-boot-app restart
}
status_service() {
service spring-boot-app status
}
stop_service() {
service spring-boot-app stop
}
force_stop_service() {
service spring-boot-app force-stop
}
await_app() {
if [ -z $1 ]
then
url=http://127.0.0.1:8080
else
url=$1
fi
end=$(date +%s)
let "end+=30"
until curl -s $url > /dev/null
do
now=$(date +%s)
if [[ $now -ge $end ]]; then
break
fi
sleep 1
done
}

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-integration-tests</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-security-tests</artifactId>
<packaging>pom</packaging>
<name>Spring Boot Security Tests</name>
<description>${project.name}</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<modules>
<module>spring-boot-security-test-web-helloworld</module>
</modules>
</project>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-security-tests</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>spring-boot-security-test-web-helloworld</artifactId>
<name>Spring Boot Security Tests - Web Hello World</name>
<description>${project.name}</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,38 @@
/*
* 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 sample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@SpringBootApplication
public class HelloWebSecurityApplication {
@Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() throws Exception {
return new InMemoryUserDetailsManager(
User.withUsername("user").password("password").roles("USER").build());
}
public static void main(String[] args) {
SpringApplication.run(HelloWebSecurityApplication.class, args);
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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 sample;
import java.util.Base64;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
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.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class HelloWebSecurityApplicationTests {
@Autowired
private FilterChainProxy springSecurityFilterChain;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private MockFilterChain chain;
@Before
public void setup() {
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
this.chain = new MockFilterChain();
}
@Test
public void requiresAuthentication() throws Exception {
this.request.addHeader("Accept", "application/json");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus())
.isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@Test
public void userAuthenticates() throws Exception {
this.request.addHeader("Accept", "application/json");
this.request.addHeader("Authorization", "Basic " + new String(
Base64.getEncoder().encode("user:password".getBytes("UTF-8"))));
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
}
}

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<settings>
<profiles>
<profile>
<id>it-repo</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<repository>
<id>local.central</id>
<url>@localRepositoryUrl@</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>local.central</id>
<url>@localRepositoryUrl@</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</settings>