Skip to content

Commit d31b3c1

Browse files
committed
Add a test case demonstrating performance bug
1 parent 8439195 commit d31b3c1

File tree

8 files changed

+330
-0
lines changed

8 files changed

+330
-0
lines changed

springdoc-openapi-starter-webmvc-api/pom.xml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@
3838
<artifactId>hibernate-validator</artifactId>
3939
<scope>test</scope>
4040
</dependency>
41+
<dependency>
42+
<groupId>org.apache.groovy</groupId>
43+
<artifactId>groovy</artifactId>
44+
<scope>test</scope>
45+
</dependency>
4146
</dependencies>
4247
<build>
4348
<plugins>
@@ -53,6 +58,43 @@
5358
</archive>
5459
</configuration>
5560
</plugin>
61+
<plugin>
62+
<groupId>org.codehaus.mojo</groupId>
63+
<artifactId>build-helper-maven-plugin</artifactId>
64+
<executions>
65+
<execution>
66+
<id>add-generated-test-sources</id>
67+
<phase>generate-test-sources</phase>
68+
<goals>
69+
<goal>add-test-source</goal>
70+
</goals>
71+
<configuration>
72+
<sources>
73+
<source>target/generated-test-sources</source>
74+
</sources>
75+
</configuration>
76+
</execution>
77+
</executions>
78+
</plugin>
79+
<plugin>
80+
<groupId>org.codehaus.gmavenplus</groupId>
81+
<artifactId>gmavenplus-plugin</artifactId>
82+
<version>${gmavenplus-plugin.version}</version>
83+
<executions>
84+
<execution>
85+
<id>generate-dtos</id>
86+
<phase>generate-test-sources</phase>
87+
<goals>
88+
<goal>execute</goal>
89+
</goals>
90+
<configuration>
91+
<scripts>
92+
<script>${basedir}/src/test/groovy/v31/app245/generate-dtos.groovy</script>
93+
</scripts>
94+
</configuration>
95+
</execution>
96+
</executions>
97+
</plugin>
5698
</plugins>
5799
</build>
58100
</project>
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package v31.app245
2+
3+
import java.nio.file.Files
4+
import java.nio.file.Path
5+
import java.nio.file.Paths
6+
7+
BASE_PACKAGE = 'test.org.springdoc.api.v31.app245'
8+
9+
def parse(String qualifiedClassName) {
10+
parts = qualifiedClassName.split('\\.')
11+
packages = parts[0..-2]
12+
className = parts[-1]
13+
return [className, packages]
14+
}
15+
16+
def package_name(List<String> packages) {
17+
return String.join('.', [BASE_PACKAGE] + packages)
18+
}
19+
20+
def directory(List<String> packages) {
21+
packages = BASE_PACKAGE.split('\\.') + packages
22+
return Paths.get(project.build.directory, "generated-test-sources", *packages)
23+
}
24+
25+
Path filePath(String className, List<String> packages) {
26+
return directory(packages).resolve(className + ".java")
27+
}
28+
29+
def generateDTO(String className, properties) {
30+
(className, packages) = parse(className)
31+
def package_ = package_name(packages)
32+
def path = filePath(className, packages)
33+
Files.createDirectories(path.parent)
34+
Files.newBufferedWriter(path).withWriter { w ->
35+
w << "package ${package_};\n\n"
36+
w << "public class ${className} {\n\n"
37+
properties.each { type, name ->
38+
w << "\tprivate $type $name;\n\n"
39+
}
40+
w << "\tpublic $className() {}\n\n"
41+
properties.each { type, name ->
42+
def capitalized = name.capitalize()
43+
w << "\tpublic $type get$capitalized() {\n"
44+
w << "\t\treturn this.$name;\n"
45+
w << "\t}\n\n"
46+
w << "\tpublic void set$capitalized($type $name) {\n"
47+
w << "\t\tthis.$name = $name;\n"
48+
w << "\t}\n\n"
49+
}
50+
w << "}\n\n"
51+
}
52+
}
53+
54+
def generateController(String className, String requestPath) {
55+
def imports = [
56+
'java.util.List',
57+
'org.springframework.http.ResponseEntity',
58+
'org.springframework.web.bind.annotation.GetMapping',
59+
'org.springframework.web.bind.annotation.PostMapping',
60+
'org.springframework.web.bind.annotation.RequestMapping',
61+
'org.springframework.web.bind.annotation.RestController',
62+
'test.org.springdoc.api.v31.app245.dto.PersonDTO'
63+
]
64+
65+
(className, packages) = parse(className)
66+
def package_ = package_name(packages)
67+
def path = filePath(className, packages)
68+
Files.createDirectories(path.parent)
69+
Files.newBufferedWriter(path).withWriter { w ->
70+
w << "package ${package_};\n\n"
71+
imports.each { imp -> w << "import $imp;\n\n" }
72+
w << "\n@RestController\n"
73+
w << "@RequestMapping(\"$requestPath\")\n"
74+
w << "public class ${className} {\n\n"
75+
w << "\t@PostMapping\n"
76+
w << "\tpublic ResponseEntity<PersonDTO> createPerson(PersonDTO createDto) {\n"
77+
w << "\t\treturn null;\n"
78+
w << "\t}\n\n"
79+
w << "\t@GetMapping\n"
80+
w << "\tpublic ResponseEntity<List<PersonDTO>> listPerson() {\n"
81+
w << "\t\treturn null;\n"
82+
w << "\t}\n\n"
83+
w << "\t@GetMapping(\"{id}\")\n"
84+
w << "\tpublic ResponseEntity<PersonDTO> getPerson(Long id) {\n"
85+
w << "\t\treturn null;\n"
86+
w << "\t}\n\n"
87+
w << "}\n"
88+
}
89+
}
90+
91+
def main() {
92+
generateDTO('dto.PersonDTO', [
93+
['Long', 'id'],
94+
['String', 'name'],
95+
['Address', 'address'],
96+
['PersonDetails1', 'details']
97+
])
98+
99+
def numControllers = 200
100+
def numDtos = 512
101+
(1..numDtos).each { i ->
102+
def className = "dto.PersonDetails$i"
103+
def props = [['String', 'value']]
104+
if (2 * i <= numDtos) {
105+
props << ["PersonDetails${2 * i}", 'details1']
106+
}
107+
if (2 * i + 1 <= numDtos) {
108+
props << ["PersonDetails${2 * i + 1}", 'details2']
109+
}
110+
generateDTO(className, props)
111+
}
112+
113+
(1..numControllers).each { i ->
114+
generateController("controller.Hello${i}Controller", "test${i}")
115+
}
116+
117+
Paths.get(project.build.directory, "generated-test-sources")
118+
.toFile()
119+
.eachFileRecurse {file ->
120+
println(file)
121+
}
122+
}
123+
124+
main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package test.org.springdoc.api.v31.app245;
2+
3+
import java.time.Duration;
4+
5+
import org.junit.jupiter.api.Test;
6+
import org.springdoc.core.utils.Constants;
7+
import org.springframework.boot.autoconfigure.SpringBootApplication;
8+
import org.springframework.boot.test.context.SpringBootTest;
9+
import test.org.springdoc.api.AbstractCommonTest;
10+
import static org.hamcrest.Matchers.is;
11+
import static org.junit.jupiter.api.Assertions.*;
12+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
13+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
14+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
15+
16+
@SpringBootTest(properties = {
17+
"logging.level.io.swagger=DEBUG"
18+
})
19+
public class SpringDocApp245Test extends AbstractCommonTest {
20+
21+
public static String className;
22+
23+
@Test
24+
void performanceTest() {
25+
className = getClass().getSimpleName();
26+
assertTimeout(Duration.ofSeconds(2), () -> {
27+
mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL)).andExpect(status().isOk())
28+
.andExpect(jsonPath("$.openapi", is("3.1.0"))).andReturn();
29+
});
30+
}
31+
32+
@SpringBootApplication
33+
static class SpringDocTestApp {
34+
35+
}
36+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package test.org.springdoc.api.v31.app245.dto;
2+
3+
public class Address {
4+
private AddressLine line1;
5+
6+
private AddressLine line2;
7+
8+
private AddressCity city;
9+
10+
private AddressState state;
11+
12+
private AddressCountry country;
13+
14+
public Address() {}
15+
16+
public AddressLine getLine1() {
17+
return line1;
18+
}
19+
20+
public void setLine1(AddressLine line1) {
21+
this.line1 = line1;
22+
}
23+
24+
public AddressLine getLine2() {
25+
return line2;
26+
}
27+
28+
public void setLine2(AddressLine line2) {
29+
this.line2 = line2;
30+
}
31+
32+
public AddressCity getCity() {
33+
return city;
34+
}
35+
36+
public void setCity(AddressCity city) {
37+
this.city = city;
38+
}
39+
40+
public AddressState getState() {
41+
return state;
42+
}
43+
44+
public void setState(AddressState state) {
45+
this.state = state;
46+
}
47+
48+
public AddressCountry getCountry() {
49+
return country;
50+
}
51+
52+
public void setCountry(AddressCountry country) {
53+
this.country = country;
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package test.org.springdoc.api.v31.app245.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonValue;
5+
6+
public class AddressCity {
7+
private final String value;
8+
9+
@JsonCreator
10+
public AddressCity(String value) {
11+
this.value = value;
12+
}
13+
14+
@JsonValue
15+
public String getValue() {
16+
return value;
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package test.org.springdoc.api.v31.app245.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonValue;
5+
6+
public class AddressCountry {
7+
private final String value;
8+
9+
@JsonCreator
10+
public AddressCountry(String value) {
11+
this.value = value;
12+
}
13+
14+
@JsonValue
15+
public String getValue() {
16+
return value;
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package test.org.springdoc.api.v31.app245.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import com.fasterxml.jackson.annotation.JsonValue;
6+
7+
public class AddressLine {
8+
private final String value;
9+
10+
@JsonCreator
11+
public AddressLine(@JsonProperty("value") String value) {
12+
this.value = value;
13+
}
14+
15+
@JsonValue
16+
public String getValue() {
17+
return value;
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package test.org.springdoc.api.v31.app245.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonValue;
5+
6+
public class AddressState {
7+
private final String value;
8+
9+
@JsonCreator
10+
public AddressState(String value) {
11+
this.value = value;
12+
}
13+
14+
@JsonValue
15+
public String getValue() {
16+
return value;
17+
}
18+
}

0 commit comments

Comments
 (0)