Added test infrustructure for generated JARs

Added tests for simply types as well as type conversion

polishing
This commit is contained in:
Oleg Zhurakousky
2019-08-06 16:04:04 +02:00
parent d3b31a6f6b
commit b51cc5ce03
12 changed files with 439 additions and 67 deletions

View File

@@ -30,6 +30,11 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
@@ -37,6 +42,47 @@
</dependency>
</dependencies>
<build>
<plugins>
<!-- <plugin> -->
<!-- <groupId>org.springframework.boot</groupId> -->
<!-- <artifactId>spring-boot-maven-plugin</artifactId> -->
<!-- <dependencies> -->
<!-- <dependency> -->
<!-- <groupId>org.springframework.boot.experimental</groupId> -->
<!-- <artifactId>spring-boot-thin-layout</artifactId> -->
<!-- <version>${wrapper.version}</version> -->
<!-- </dependency> -->
<!-- </dependencies> -->
<!-- </plugin> -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-invoker-plugin</artifactId>
<version>3.0.1</version>
<configuration>
<localRepositoryPath>${project.build.directory}/local-repo
</localRepositoryPath>
</configuration>
<executions>
<execution>
<id>prepare-test</id>
<phase>test-compile</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<cloneProjectsTo>${project.build.directory}/it
</cloneProjectsTo>
<settingsFile>src/it/settings.xml</settingsFile>
<addTestClassPath>true</addTestClassPath>
<streamLogs>true</streamLogs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>function.example</groupId>
<artifactId>bootapp</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.BUILD-SNAPSHOT</version>
<relativePath/>
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud-function.version>3.0.0.BUILD-SNAPSHOT</spring-cloud-function.version>
<wrapper.version>1.0.17.RELEASE</wrapper.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack</id>
<phase>package</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
<classifier>exec</classifier>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,57 @@
package function.example;
import java.util.function.Function;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class SimpleFunctionAppApplication {
public static void main(String[] args) {
SpringApplication.run(SimpleFunctionAppApplication.class, args);
}
@Bean
public Function<String, String> uppercase() {
System.out.println("==> CREATING 'uppercase' FUNCTION bean");
return new UpperCaseFunction();
}
@Bean
public Function<Person, Person> uppercasePerson() {
System.out.println("==> CREATING 'uppercasePerson' FUNCTION bean");
return person -> {
Person p = new Person();
p.setId(person.getId());
p.setName(person.getName().toUpperCase());
return p;
};
}
public static class Person {
private String name;
private int id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
}

View File

@@ -0,0 +1,13 @@
package function.example;
import java.util.function.Function;
public class UpperCaseFunction implements Function<String, String> {
@Override
public String apply(String value) {
System.out.println("Uppercasing " + value);
return value.toUpperCase();
}
}

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>function.example</groupId>
<artifactId>bootjar</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.BUILD-SNAPSHOT</version>
<relativePath/>
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud-function.version>3.0.0.BUILD-SNAPSHOT</spring-cloud-function.version>
<wrapper.version>1.0.17.RELEASE</wrapper.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack</id>
<phase>package</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
<classifier>exec</classifier>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,38 @@
package function.example;
import java.util.function.Function;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
public class SimpleFunctionAppApplication {
public static void main(String[] args) {
SpringApplication.run(SimpleFunctionAppApplication.class, args);
}
public static class Person {
private String name;
private int id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
}

View File

@@ -0,0 +1,13 @@
package function.example;
import java.util.function.Function;
public class UpperCaseFunction implements Function<String, String> {
@Override
public String apply(String value) {
System.out.println("Uppercasing " + value);
return value.toUpperCase();
}
}

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>

View File

@@ -32,14 +32,11 @@ public abstract class ApplicationContainer {
private final FunctionProperties functionProperties;
private final Object function;
public ApplicationContainer(FunctionCatalog functionCatalog,
FunctionInspector functionInspector, FunctionProperties functionProperties) {
this.functionCatalog = functionCatalog;
this.functionInspector = functionInspector;
this.functionProperties = functionProperties;
this.function = this.functionCatalog.lookup(this.functionProperties.getFunctionName());
}
protected FunctionCatalog getFunctionCatalog() {
@@ -56,6 +53,11 @@ public abstract class ApplicationContainer {
@SuppressWarnings("unchecked")
public <T> T getFunction() {
return (T) this.function;
return (T) this.functionCatalog.lookup(this.functionProperties.getFunctionName());
}
@SuppressWarnings("unchecked")
public <T> T getFunction(String... acceptedOutputMimeTypes) {
return (T) this.functionCatalog.lookup(this.functionProperties.getFunctionName(), acceptedOutputMimeTypes);
}
}

View File

@@ -31,12 +31,19 @@ import org.springframework.util.StringUtils;
public class FunctionProperties {
/**
* Location(s) of jar archives containing the supplier/function/consumer class to run.
* Location of jar archive containing the supplier/function/consumer class or bean to run.
*/
private String location;
/**
* The name of the function to be looked up from the FunctionCatalog (e.g., bean name).
*/
private String functionName;
/**
* The name of the function class tyo be instantiated and loaded into FunctionCatalog. The name of the
* function will be decapitalized simple name of this class.
*/
private String functionClass;
public void setFunctionClass(String functionClass) {

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2017-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.function.deployer;
import java.nio.charset.StandardCharsets;
import java.util.function.Function;
import org.junit.Test;
import org.springframework.cloud.function.context.FunctionCatalog;
import org.springframework.cloud.function.context.catalog.FunctionInspector;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import static org.assertj.core.api.Assertions.assertThat;
/**
*
* @author Oleg Zhurakousky
* @since 3.0
*/
public class ApplicationContainerTests {
@Test
public void testCustomApplicationContainerWithBootJar() throws Exception {
String[] args = new String[] {"--spring.cloud.function.location=target/it/bootjar/target/bootjar-0.0.1.BUILD-SNAPSHOT-exec.jar",
"--spring.cloud.function.function-class=function.example.UpperCaseFunction"};
JavaInvoker invokerByClass =
FunctionDeployerBootstrap.instance(args).run(JavaInvoker.class, args);
assertThat(invokerByClass.uppercaseSimple("bob")).isEqualTo("BOB");
assertThat(invokerByClass.uppercaseSimple("stacy")).isEqualTo("STACY");
}
@Test
public void testCustomApplicationContainerWithBootAppSimpleTypes() throws Exception {
String[] args = new String[] {"--spring.cloud.function.location=target/it/bootapp/target/bootapp-0.0.1.BUILD-SNAPSHOT-exec.jar",
"--spring.cloud.function.function-name=uppercase"};
JavaInvoker invokerByBean =
FunctionDeployerBootstrap.instance(args).run(JavaInvoker.class, args);
Message<byte[]> result = invokerByBean.uppercase(MessageBuilder.withPayload("\"bob\"".getBytes(StandardCharsets.UTF_8)).build());
assertThat(new String(result.getPayload(), StandardCharsets.UTF_8)).isEqualTo("\"BOB\"");
}
@Test
public void testCustomApplicationContainerWithBootAppWithTypeConversion() throws Exception {
String[] args = new String[] {"--spring.cloud.function.location=target/it/bootapp/target/bootapp-0.0.1.BUILD-SNAPSHOT-exec.jar",
"--spring.cloud.function.function-name=uppercasePerson"};
JavaInvoker invokerByBean =
FunctionDeployerBootstrap.instance(args).run(JavaInvoker.class, args);
Message<byte[]> result = invokerByBean.uppercase(MessageBuilder.withPayload("{\"name\":\"bob\",\"id\":1}".getBytes(StandardCharsets.UTF_8)).build());
assertThat(new String(result.getPayload(), StandardCharsets.UTF_8)).isEqualTo("{\"name\":\"BOB\",\"id\":1}");
}
private static class JavaInvoker extends ApplicationContainer {
JavaInvoker(FunctionCatalog functionCatalog, FunctionInspector functionInspector,
FunctionProperties functionProperties) {
super(functionCatalog, functionInspector, functionProperties);
}
public Message<byte[]> uppercase(Message<byte[]> input) {
Function<Message<byte[]>, Message<byte[]>> functon = this.getFunction("application/json");
return functon.apply(input);
}
public String uppercaseSimple(String input) {
Function<String, String> functon = this.getFunction();
return functon.apply(input);
}
}
}

View File

@@ -1,62 +0,0 @@
/*
* Copyright 2017-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.function.deployer.sample;
import java.util.function.Function;
import org.springframework.cloud.function.context.FunctionCatalog;
import org.springframework.cloud.function.context.catalog.FunctionInspector;
import org.springframework.cloud.function.deployer.ApplicationContainer;
import org.springframework.cloud.function.deployer.FunctionDeployerBootstrap;
import org.springframework.cloud.function.deployer.FunctionProperties;
/**
*
* @author Oleg Zhurakousky
* @since 3.0
*/
public class SampleInvoker extends ApplicationContainer {
public static void main(String[] args) throws Exception {
SampleInvoker invokerByClass = FunctionDeployerBootstrap.instance(
"--spring.cloud.function.location=/Users/olegz/Downloads/simple-function-app/jars/simple-function-jar-0.0.1-SNAPSHOT.jar",
"--spring.cloud.function.function-class=oz.function.simplefunctionapp.UpperCaseFunction")
.run(SampleInvoker.class, args);
System.out.println(invokerByClass.uppercase("eric"));
System.out.println(invokerByClass.uppercase("oleg"));
SampleInvoker invokerByBean = FunctionDeployerBootstrap.instance(
"--spring.cloud.function.location=/Users/olegz/Downloads/simple-function-app/jars/simple-function-app-0.0.1-SNAPSHOT.jar",
"--spring.cloud.function.function-name=uppercase")
.run(SampleInvoker.class, args);
System.out.println(invokerByBean.uppercase("eric"));
System.out.println(invokerByBean.uppercase("oleg"));
}
public SampleInvoker(FunctionCatalog functionCatalog, FunctionInspector functionInspector,
FunctionProperties functionProperties) {
super(functionCatalog, functionInspector, functionProperties);
}
public String uppercase(String value) {
Function<String, String> functon = this.getFunction();
return functon.apply(value);
}
}