Skip to content

Commit bbfe1ec

Browse files
authored
[Tests] Mutualize fixtures code in BaseHttpFixture (#31210)
Many fixtures have similar code for writing the pid & ports files or for handling HTTP requests. This commit adds an AbstractHttpFixture class in the test framework that can be extended for specific testing purposes.
1 parent ce245a7 commit bbfe1ec

File tree

17 files changed

+1631
-2105
lines changed

17 files changed

+1631
-2105
lines changed

modules/repository-url/build.gradle

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,6 @@ esplugin {
2323
classname 'org.elasticsearch.plugin.repository.url.URLRepositoryPlugin'
2424
}
2525

26-
forbiddenApisTest {
27-
// we are using jdk-internal instead of jdk-non-portable to allow for com.sun.net.httpserver.* usage
28-
bundledSignatures -= 'jdk-non-portable'
29-
bundledSignatures += 'jdk-internal'
30-
}
31-
3226
// This directory is shared between two URL repositories and one FS repository in YAML integration tests
3327
File repositoryDir = new File(project.buildDir, "shared-repository")
3428

modules/repository-url/src/test/java/org/elasticsearch/repositories/url/URLFixture.java

Lines changed: 36 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -18,151 +18,71 @@
1818
*/
1919
package org.elasticsearch.repositories.url;
2020

21-
import com.sun.net.httpserver.HttpExchange;
22-
import com.sun.net.httpserver.HttpHandler;
23-
import com.sun.net.httpserver.HttpServer;
21+
import org.elasticsearch.test.fixture.AbstractHttpFixture;
2422
import org.elasticsearch.common.SuppressForbidden;
25-
import org.elasticsearch.mocksocket.MockHttpServer;
2623
import org.elasticsearch.rest.RestStatus;
2724

2825
import java.io.IOException;
29-
import java.lang.management.ManagementFactory;
30-
import java.net.Inet6Address;
31-
import java.net.InetAddress;
32-
import java.net.InetSocketAddress;
33-
import java.net.SocketAddress;
34-
import java.nio.charset.StandardCharsets;
3526
import java.nio.file.Files;
3627
import java.nio.file.Path;
3728
import java.nio.file.Paths;
38-
import java.nio.file.StandardCopyOption;
29+
import java.util.HashMap;
3930
import java.util.Map;
40-
import java.util.Objects;
41-
42-
import static java.nio.charset.StandardCharsets.UTF_8;
43-
import static java.util.Collections.emptyMap;
44-
import static java.util.Collections.singleton;
45-
import static java.util.Collections.singletonMap;
4631

4732
/**
4833
* This {@link URLFixture} exposes a filesystem directory over HTTP. It is used in repository-url
4934
* integration tests to expose a directory created by a regular FS repository.
5035
*/
51-
public class URLFixture {
36+
public class URLFixture extends AbstractHttpFixture {
37+
38+
private final Path repositoryDir;
39+
40+
/**
41+
* Creates a {@link URLFixture}
42+
*/
43+
private URLFixture(final String workingDir, final String repositoryDir) {
44+
super(workingDir);
45+
this.repositoryDir = dir(repositoryDir);
46+
}
5247

5348
public static void main(String[] args) throws Exception {
5449
if (args == null || args.length != 2) {
5550
throw new IllegalArgumentException("URLFixture <working directory> <repository directory>");
5651
}
5752

58-
final InetSocketAddress socketAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
59-
final HttpServer httpServer = MockHttpServer.createHttp(socketAddress, 0);
60-
61-
try {
62-
final Path workingDirectory = dir(args[0]);
63-
/// Writes the PID of the current Java process in a `pid` file located in the working directory
64-
writeFile(workingDirectory, "pid", ManagementFactory.getRuntimeMXBean().getName().split("@")[0]);
65-
66-
final String addressAndPort = addressToString(httpServer.getAddress());
67-
// Writes the address and port of the http server in a `ports` file located in the working directory
68-
writeFile(workingDirectory, "ports", addressAndPort);
69-
70-
// Exposes the repository over HTTP
71-
httpServer.createContext("/", new ResponseHandler(dir(args[1])));
72-
httpServer.start();
73-
74-
// Wait to be killed
75-
Thread.sleep(Long.MAX_VALUE);
76-
77-
} finally {
78-
httpServer.stop(0);
79-
}
80-
}
81-
82-
@SuppressForbidden(reason = "Paths#get is fine - we don't have environment here")
83-
private static Path dir(final String dir) {
84-
return Paths.get(dir);
85-
}
86-
87-
private static void writeFile(final Path dir, final String fileName, final String content) throws IOException {
88-
final Path tempPidFile = Files.createTempFile(dir, null, null);
89-
Files.write(tempPidFile, singleton(content));
90-
Files.move(tempPidFile, dir.resolve(fileName), StandardCopyOption.ATOMIC_MOVE);
53+
final URLFixture fixture = new URLFixture(args[0], args[1]);
54+
fixture.listen();
9155
}
9256

93-
private static String addressToString(final SocketAddress address) {
94-
final InetSocketAddress inetSocketAddress = (InetSocketAddress) address;
95-
if (inetSocketAddress.getAddress() instanceof Inet6Address) {
96-
return "[" + inetSocketAddress.getHostString() + "]:" + inetSocketAddress.getPort();
97-
} else {
98-
return inetSocketAddress.getHostString() + ":" + inetSocketAddress.getPort();
99-
}
100-
}
101-
102-
static class ResponseHandler implements HttpHandler {
103-
104-
private final Path repositoryDir;
105-
106-
ResponseHandler(final Path repositoryDir) {
107-
this.repositoryDir = repositoryDir;
108-
}
109-
110-
@Override
111-
public void handle(HttpExchange exchange) throws IOException {
112-
Response response;
113-
114-
final String userAgent = exchange.getRequestHeaders().getFirst("User-Agent");
115-
if (userAgent != null && userAgent.startsWith("Apache Ant")) {
116-
// This is a request made by the AntFixture, just reply "OK"
117-
response = new Response(RestStatus.OK, emptyMap(), "text/plain; charset=utf-8", "OK".getBytes(UTF_8));
118-
119-
} else if ("GET".equalsIgnoreCase(exchange.getRequestMethod())) {
120-
String path = exchange.getRequestURI().toString();
121-
if (path.length() > 0 && path.charAt(0) == '/') {
122-
path = path.substring(1);
123-
}
57+
@Override
58+
protected AbstractHttpFixture.Response handle(final Request request) throws IOException {
59+
if ("GET".equalsIgnoreCase(request.getMethod())) {
60+
String path = request.getPath();
61+
if (path.length() > 0 && path.charAt(0) == '/') {
62+
path = path.substring(1);
63+
}
12464

125-
Path normalizedRepositoryDir = repositoryDir.normalize();
126-
Path normalizedPath = normalizedRepositoryDir.resolve(path).normalize();
65+
Path normalizedRepositoryDir = repositoryDir.normalize();
66+
Path normalizedPath = normalizedRepositoryDir.resolve(path).normalize();
12767

128-
if (normalizedPath.startsWith(normalizedRepositoryDir)) {
129-
if (Files.exists(normalizedPath) && Files.isReadable(normalizedPath) && Files.isRegularFile(normalizedPath)) {
130-
byte[] content = Files.readAllBytes(normalizedPath);
131-
Map<String, String> headers = singletonMap("Content-Length", String.valueOf(content.length));
132-
response = new Response(RestStatus.OK, headers, "application/octet-stream", content);
133-
} else {
134-
response = new Response(RestStatus.NOT_FOUND, emptyMap(), "text/plain; charset=utf-8", new byte[0]);
135-
}
68+
if (normalizedPath.startsWith(normalizedRepositoryDir)) {
69+
if (Files.exists(normalizedPath) && Files.isReadable(normalizedPath) && Files.isRegularFile(normalizedPath)) {
70+
byte[] content = Files.readAllBytes(normalizedPath);
71+
final Map<String, String> headers = new HashMap<>(contentType("application/octet-stream"));
72+
headers.put("Content-Length", String.valueOf(content.length));
73+
return new Response(RestStatus.OK.getStatus(), headers, content);
13674
} else {
137-
response = new Response(RestStatus.FORBIDDEN, emptyMap(), "text/plain; charset=utf-8", new byte[0]);
75+
return new Response(RestStatus.NOT_FOUND.getStatus(), TEXT_PLAIN_CONTENT_TYPE, EMPTY_BYTE);
13876
}
13977
} else {
140-
response = new Response(RestStatus.INTERNAL_SERVER_ERROR, emptyMap(), "text/plain; charset=utf-8",
141-
"Unsupported HTTP method".getBytes(StandardCharsets.UTF_8));
78+
return new Response(RestStatus.FORBIDDEN.getStatus(), TEXT_PLAIN_CONTENT_TYPE, EMPTY_BYTE);
14279
}
143-
exchange.sendResponseHeaders(response.status.getStatus(), response.body.length);
144-
if (response.body.length > 0) {
145-
exchange.getResponseBody().write(response.body);
146-
}
147-
exchange.close();
14880
}
81+
return null;
14982
}
15083

151-
/**
152-
* Represents a HTTP Response.
153-
*/
154-
static class Response {
155-
156-
final RestStatus status;
157-
final Map<String, String> headers;
158-
final String contentType;
159-
final byte[] body;
160-
161-
Response(final RestStatus status, final Map<String, String> headers, final String contentType, final byte[] body) {
162-
this.status = Objects.requireNonNull(status);
163-
this.headers = Objects.requireNonNull(headers);
164-
this.contentType = Objects.requireNonNull(contentType);
165-
this.body = Objects.requireNonNull(body);
166-
}
84+
@SuppressForbidden(reason = "Paths#get is fine - we don't have environment here")
85+
private static Path dir(final String dir) {
86+
return Paths.get(dir);
16787
}
16888
}

plugins/examples/rest-handler/build.gradle

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,12 @@ esplugin {
2828
// No unit tests in this example
2929
test.enabled = false
3030

31-
configurations {
32-
exampleFixture
33-
}
34-
35-
dependencies {
36-
exampleFixture project(':test:fixtures:example-fixture')
37-
}
38-
3931
task exampleFixture(type: org.elasticsearch.gradle.test.AntFixture) {
40-
dependsOn project.configurations.exampleFixture
32+
dependsOn testClasses
4133
executable = new File(project.runtimeJavaHome, 'bin/java')
42-
args '-cp', "${ -> project.configurations.exampleFixture.asPath }",
43-
'example.ExampleTestFixture',
44-
baseDir
34+
args '-cp', "${ -> project.sourceSets.test.runtimeClasspath.asPath }",
35+
'org.elasticsearch.example.resthandler.ExampleFixture',
36+
baseDir, 'TEST'
4537
}
4638

4739
integTestCluster {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.example.resthandler;
20+
21+
import org.elasticsearch.test.fixture.AbstractHttpFixture;
22+
23+
import java.io.IOException;
24+
import java.util.Objects;
25+
26+
import static java.nio.charset.StandardCharsets.UTF_8;
27+
28+
public class ExampleFixture extends AbstractHttpFixture {
29+
30+
private final String message;
31+
32+
private ExampleFixture(final String workingDir, final String message) {
33+
super(workingDir);
34+
this.message = Objects.requireNonNull(message);
35+
}
36+
37+
@Override
38+
protected Response handle(final Request request) throws IOException {
39+
if ("GET".equals(request.getMethod()) && "/".equals(request.getPath())) {
40+
return new Response(200, TEXT_PLAIN_CONTENT_TYPE, message.getBytes(UTF_8));
41+
}
42+
return null;
43+
}
44+
45+
public static void main(final String[] args) throws Exception {
46+
if (args == null || args.length != 2) {
47+
throw new IllegalArgumentException("ExampleFixture <working directory> <echo message>");
48+
}
49+
50+
final ExampleFixture fixture = new ExampleFixture(args[0], args[1]);
51+
fixture.listen();
52+
}
53+
}

plugins/repository-azure/qa/microsoft-azure-storage/build.gradle

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,10 @@ import org.elasticsearch.gradle.test.AntFixture
2323
apply plugin: 'elasticsearch.standalone-rest-test'
2424
apply plugin: 'elasticsearch.rest-test'
2525

26-
dependencies {
27-
testCompile project(path: ':plugins:repository-azure', configuration: 'runtime')
28-
}
29-
3026
integTestCluster {
3127
plugin ':plugins:repository-azure'
3228
}
3329

34-
forbiddenApisTest {
35-
// we are using jdk-internal instead of jdk-non-portable to allow for com.sun.net.httpserver.* usage
36-
bundledSignatures -= 'jdk-non-portable'
37-
bundledSignatures += 'jdk-internal'
38-
}
39-
4030
boolean useFixture = false
4131

4232
String azureAccount = System.getenv("azure_storage_account")
@@ -54,7 +44,7 @@ if (!azureAccount && !azureKey && !azureContainer && !azureBasePath) {
5444

5545
/** A task to start the fixture which emulates an Azure Storage service **/
5646
task azureStorageFixture(type: AntFixture) {
57-
dependsOn compileTestJava
47+
dependsOn testClasses
5848
env 'CLASSPATH', "${ -> project.sourceSets.test.runtimeClasspath.asPath }"
5949
executable = new File(project.runtimeJavaHome, 'bin/java')
6050
args 'org.elasticsearch.repositories.azure.AzureStorageFixture', baseDir, azureContainer
@@ -64,6 +54,7 @@ Map<String, Object> expansions = [
6454
'container': azureContainer,
6555
'base_path': azureBasePath
6656
]
57+
6758
processTestResources {
6859
inputs.properties(expansions)
6960
MavenFilteringHack.filter(it, expansions)

0 commit comments

Comments
 (0)