Skip to content

Commit

Permalink
[Tests] Mutualize fixtures code in BaseHttpFixture (#31210)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
tlrx authored Jun 14, 2018
1 parent ce245a7 commit bbfe1ec
Show file tree
Hide file tree
Showing 17 changed files with 1,631 additions and 2,105 deletions.
6 changes: 0 additions & 6 deletions modules/repository-url/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,6 @@ esplugin {
classname 'org.elasticsearch.plugin.repository.url.URLRepositoryPlugin'
}

forbiddenApisTest {
// we are using jdk-internal instead of jdk-non-portable to allow for com.sun.net.httpserver.* usage
bundledSignatures -= 'jdk-non-portable'
bundledSignatures += 'jdk-internal'
}

// This directory is shared between two URL repositories and one FS repository in YAML integration tests
File repositoryDir = new File(project.buildDir, "shared-repository")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,151 +18,71 @@
*/
package org.elasticsearch.repositories.url;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import org.elasticsearch.test.fixture.AbstractHttpFixture;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.mocksocket.MockHttpServer;
import org.elasticsearch.rest.RestStatus;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonMap;

/**
* This {@link URLFixture} exposes a filesystem directory over HTTP. It is used in repository-url
* integration tests to expose a directory created by a regular FS repository.
*/
public class URLFixture {
public class URLFixture extends AbstractHttpFixture {

private final Path repositoryDir;

/**
* Creates a {@link URLFixture}
*/
private URLFixture(final String workingDir, final String repositoryDir) {
super(workingDir);
this.repositoryDir = dir(repositoryDir);
}

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

final InetSocketAddress socketAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
final HttpServer httpServer = MockHttpServer.createHttp(socketAddress, 0);

try {
final Path workingDirectory = dir(args[0]);
/// Writes the PID of the current Java process in a `pid` file located in the working directory
writeFile(workingDirectory, "pid", ManagementFactory.getRuntimeMXBean().getName().split("@")[0]);

final String addressAndPort = addressToString(httpServer.getAddress());
// Writes the address and port of the http server in a `ports` file located in the working directory
writeFile(workingDirectory, "ports", addressAndPort);

// Exposes the repository over HTTP
httpServer.createContext("/", new ResponseHandler(dir(args[1])));
httpServer.start();

// Wait to be killed
Thread.sleep(Long.MAX_VALUE);

} finally {
httpServer.stop(0);
}
}

@SuppressForbidden(reason = "Paths#get is fine - we don't have environment here")
private static Path dir(final String dir) {
return Paths.get(dir);
}

private static void writeFile(final Path dir, final String fileName, final String content) throws IOException {
final Path tempPidFile = Files.createTempFile(dir, null, null);
Files.write(tempPidFile, singleton(content));
Files.move(tempPidFile, dir.resolve(fileName), StandardCopyOption.ATOMIC_MOVE);
final URLFixture fixture = new URLFixture(args[0], args[1]);
fixture.listen();
}

private static String addressToString(final SocketAddress address) {
final InetSocketAddress inetSocketAddress = (InetSocketAddress) address;
if (inetSocketAddress.getAddress() instanceof Inet6Address) {
return "[" + inetSocketAddress.getHostString() + "]:" + inetSocketAddress.getPort();
} else {
return inetSocketAddress.getHostString() + ":" + inetSocketAddress.getPort();
}
}

static class ResponseHandler implements HttpHandler {

private final Path repositoryDir;

ResponseHandler(final Path repositoryDir) {
this.repositoryDir = repositoryDir;
}

@Override
public void handle(HttpExchange exchange) throws IOException {
Response response;

final String userAgent = exchange.getRequestHeaders().getFirst("User-Agent");
if (userAgent != null && userAgent.startsWith("Apache Ant")) {
// This is a request made by the AntFixture, just reply "OK"
response = new Response(RestStatus.OK, emptyMap(), "text/plain; charset=utf-8", "OK".getBytes(UTF_8));

} else if ("GET".equalsIgnoreCase(exchange.getRequestMethod())) {
String path = exchange.getRequestURI().toString();
if (path.length() > 0 && path.charAt(0) == '/') {
path = path.substring(1);
}
@Override
protected AbstractHttpFixture.Response handle(final Request request) throws IOException {
if ("GET".equalsIgnoreCase(request.getMethod())) {
String path = request.getPath();
if (path.length() > 0 && path.charAt(0) == '/') {
path = path.substring(1);
}

Path normalizedRepositoryDir = repositoryDir.normalize();
Path normalizedPath = normalizedRepositoryDir.resolve(path).normalize();
Path normalizedRepositoryDir = repositoryDir.normalize();
Path normalizedPath = normalizedRepositoryDir.resolve(path).normalize();

if (normalizedPath.startsWith(normalizedRepositoryDir)) {
if (Files.exists(normalizedPath) && Files.isReadable(normalizedPath) && Files.isRegularFile(normalizedPath)) {
byte[] content = Files.readAllBytes(normalizedPath);
Map<String, String> headers = singletonMap("Content-Length", String.valueOf(content.length));
response = new Response(RestStatus.OK, headers, "application/octet-stream", content);
} else {
response = new Response(RestStatus.NOT_FOUND, emptyMap(), "text/plain; charset=utf-8", new byte[0]);
}
if (normalizedPath.startsWith(normalizedRepositoryDir)) {
if (Files.exists(normalizedPath) && Files.isReadable(normalizedPath) && Files.isRegularFile(normalizedPath)) {
byte[] content = Files.readAllBytes(normalizedPath);
final Map<String, String> headers = new HashMap<>(contentType("application/octet-stream"));
headers.put("Content-Length", String.valueOf(content.length));
return new Response(RestStatus.OK.getStatus(), headers, content);
} else {
response = new Response(RestStatus.FORBIDDEN, emptyMap(), "text/plain; charset=utf-8", new byte[0]);
return new Response(RestStatus.NOT_FOUND.getStatus(), TEXT_PLAIN_CONTENT_TYPE, EMPTY_BYTE);
}
} else {
response = new Response(RestStatus.INTERNAL_SERVER_ERROR, emptyMap(), "text/plain; charset=utf-8",
"Unsupported HTTP method".getBytes(StandardCharsets.UTF_8));
return new Response(RestStatus.FORBIDDEN.getStatus(), TEXT_PLAIN_CONTENT_TYPE, EMPTY_BYTE);
}
exchange.sendResponseHeaders(response.status.getStatus(), response.body.length);
if (response.body.length > 0) {
exchange.getResponseBody().write(response.body);
}
exchange.close();
}
return null;
}

/**
* Represents a HTTP Response.
*/
static class Response {

final RestStatus status;
final Map<String, String> headers;
final String contentType;
final byte[] body;

Response(final RestStatus status, final Map<String, String> headers, final String contentType, final byte[] body) {
this.status = Objects.requireNonNull(status);
this.headers = Objects.requireNonNull(headers);
this.contentType = Objects.requireNonNull(contentType);
this.body = Objects.requireNonNull(body);
}
@SuppressForbidden(reason = "Paths#get is fine - we don't have environment here")
private static Path dir(final String dir) {
return Paths.get(dir);
}
}
16 changes: 4 additions & 12 deletions plugins/examples/rest-handler/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,12 @@ esplugin {
// No unit tests in this example
test.enabled = false

configurations {
exampleFixture
}

dependencies {
exampleFixture project(':test:fixtures:example-fixture')
}

task exampleFixture(type: org.elasticsearch.gradle.test.AntFixture) {
dependsOn project.configurations.exampleFixture
dependsOn testClasses
executable = new File(project.runtimeJavaHome, 'bin/java')
args '-cp', "${ -> project.configurations.exampleFixture.asPath }",
'example.ExampleTestFixture',
baseDir
args '-cp', "${ -> project.sourceSets.test.runtimeClasspath.asPath }",
'org.elasticsearch.example.resthandler.ExampleFixture',
baseDir, 'TEST'
}

integTestCluster {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you 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.elasticsearch.example.resthandler;

import org.elasticsearch.test.fixture.AbstractHttpFixture;

import java.io.IOException;
import java.util.Objects;

import static java.nio.charset.StandardCharsets.UTF_8;

public class ExampleFixture extends AbstractHttpFixture {

private final String message;

private ExampleFixture(final String workingDir, final String message) {
super(workingDir);
this.message = Objects.requireNonNull(message);
}

@Override
protected Response handle(final Request request) throws IOException {
if ("GET".equals(request.getMethod()) && "/".equals(request.getPath())) {
return new Response(200, TEXT_PLAIN_CONTENT_TYPE, message.getBytes(UTF_8));
}
return null;
}

public static void main(final String[] args) throws Exception {
if (args == null || args.length != 2) {
throw new IllegalArgumentException("ExampleFixture <working directory> <echo message>");
}

final ExampleFixture fixture = new ExampleFixture(args[0], args[1]);
fixture.listen();
}
}
13 changes: 2 additions & 11 deletions plugins/repository-azure/qa/microsoft-azure-storage/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,10 @@ import org.elasticsearch.gradle.test.AntFixture
apply plugin: 'elasticsearch.standalone-rest-test'
apply plugin: 'elasticsearch.rest-test'

dependencies {
testCompile project(path: ':plugins:repository-azure', configuration: 'runtime')
}

integTestCluster {
plugin ':plugins:repository-azure'
}

forbiddenApisTest {
// we are using jdk-internal instead of jdk-non-portable to allow for com.sun.net.httpserver.* usage
bundledSignatures -= 'jdk-non-portable'
bundledSignatures += 'jdk-internal'
}

boolean useFixture = false

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

/** A task to start the fixture which emulates an Azure Storage service **/
task azureStorageFixture(type: AntFixture) {
dependsOn compileTestJava
dependsOn testClasses
env 'CLASSPATH', "${ -> project.sourceSets.test.runtimeClasspath.asPath }"
executable = new File(project.runtimeJavaHome, 'bin/java')
args 'org.elasticsearch.repositories.azure.AzureStorageFixture', baseDir, azureContainer
Expand All @@ -64,6 +54,7 @@ Map<String, Object> expansions = [
'container': azureContainer,
'base_path': azureBasePath
]

processTestResources {
inputs.properties(expansions)
MavenFilteringHack.filter(it, expansions)
Expand Down
Loading

0 comments on commit bbfe1ec

Please sign in to comment.