Commit e1799e66 authored by jbudorick's avatar jbudorick Committed by Commit bot

[Android] Add a java version of the test server spawner.

BUG=448626

Review URL: https://codereview.chromium.org/867073002

Cr-Commit-Position: refs/heads/master@{#313532}
parent 61288e6c
...@@ -587,7 +587,7 @@ class DeviceUtils(object): ...@@ -587,7 +587,7 @@ class DeviceUtils(object):
if raw: if raw:
cmd.append('-r') cmd.append('-r')
for k, v in extras.iteritems(): for k, v in extras.iteritems():
cmd.extend(['-e', k, v]) cmd.extend(['-e', str(k), str(v)])
cmd.append(component) cmd.append(component)
return self.RunShellCommand(cmd, check_return=True) return self.RunShellCommand(cmd, check_return=True)
......
...@@ -24,6 +24,9 @@ _EXTRA_COMMAND_LINE_FILE = ( ...@@ -24,6 +24,9 @@ _EXTRA_COMMAND_LINE_FILE = (
'org.chromium.native_test.ChromeNativeTestActivity.CommandLineFile') 'org.chromium.native_test.ChromeNativeTestActivity.CommandLineFile')
_EXTRA_COMMAND_LINE_FLAGS = ( _EXTRA_COMMAND_LINE_FLAGS = (
'org.chromium.native_test.ChromeNativeTestActivity.CommandLineFlags') 'org.chromium.native_test.ChromeNativeTestActivity.CommandLineFlags')
_EXTRA_ENABLE_TEST_SERVER_SPAWNER = (
'org.chromium.native_test.ChromeNativeTestInstrumentationTestRunner'
'.EnableTestServerSpawner')
_MAX_SHARD_SIZE = 256 _MAX_SHARD_SIZE = 256
...@@ -39,6 +42,10 @@ class _ApkDelegate(object): ...@@ -39,6 +42,10 @@ class _ApkDelegate(object):
self._package = apk_helper.GetPackageName(self._apk) self._package = apk_helper.GetPackageName(self._apk)
self._runner = apk_helper.GetInstrumentationName(self._apk) self._runner = apk_helper.GetInstrumentationName(self._apk)
self._component = '%s/%s' % (self._package, self._runner) self._component = '%s/%s' % (self._package, self._runner)
self._enable_test_server_spawner = False
def EnableTestServerSpawner(self):
self._enable_test_server_spawner = True
def Install(self, device): def Install(self, device):
device.Install(self._apk) device.Install(self._apk)
...@@ -47,11 +54,14 @@ class _ApkDelegate(object): ...@@ -47,11 +54,14 @@ class _ApkDelegate(object):
with device_temp_file.DeviceTempFile(device.adb) as command_line_file: with device_temp_file.DeviceTempFile(device.adb) as command_line_file:
device.WriteFile(command_line_file.name, '_ %s' % flags) device.WriteFile(command_line_file.name, '_ %s' % flags)
extras = {
_EXTRA_COMMAND_LINE_FILE: command_line_file.name,
}
if self._enable_test_server_spawner:
extras[_EXTRA_ENABLE_TEST_SERVER_SPAWNER] = '1'
return device.StartInstrumentation( return device.StartInstrumentation(
self._component, self._component, extras=extras, raw=False, **kwargs)
extras={_EXTRA_COMMAND_LINE_FILE: command_line_file.name},
raw=False,
**kwargs)
def Clear(self, device): def Clear(self, device):
device.ClearApplicationState(self._package) device.ClearApplicationState(self._package)
...@@ -71,6 +81,9 @@ class _ExeDelegate(object): ...@@ -71,6 +81,9 @@ class _ExeDelegate(object):
self._deps_host_path = None self._deps_host_path = None
self._test_run = tr self._test_run = tr
def EnableTestServerSpawner(self):
pass
def Install(self, device): def Install(self, device):
# TODO(jbudorick): Look into merging this with normal data deps pushing if # TODO(jbudorick): Look into merging this with normal data deps pushing if
# executables become supported on nonlocal environments. # executables become supported on nonlocal environments.
...@@ -130,8 +143,6 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun): ...@@ -130,8 +143,6 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
elif self._test_instance.exe: elif self._test_instance.exe:
self._delegate = _ExeDelegate(self, self._test_instance.exe) self._delegate = _ExeDelegate(self, self._test_instance.exe)
self._servers = {}
#override #override
def TestPackage(self): def TestPackage(self):
return self._test_instance._suite return self._test_instance._suite
...@@ -150,14 +161,8 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun): ...@@ -150,14 +161,8 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
for h, d in host_device_tuples] for h, d in host_device_tuples]
dev.PushChangedFiles(host_device_tuples) dev.PushChangedFiles(host_device_tuples)
self._servers[str(dev)] = []
if self.TestPackage() in _SUITE_REQUIRES_TEST_SERVER_SPAWNER: if self.TestPackage() in _SUITE_REQUIRES_TEST_SERVER_SPAWNER:
self._servers[str(dev)].append( self._delegate.EnableTestServerSpawner()
local_test_server_spawner.LocalTestServerSpawner(
ports.AllocateTestServerPort(), dev, self.GetTool(dev)))
for s in self._servers[str(dev)]:
s.SetUp()
self._env.parallel_devices.pMap(individual_device_set_up, self._env.parallel_devices.pMap(individual_device_set_up,
self._test_instance.GetDataDependencies()) self._test_instance.GetDataDependencies())
...@@ -189,8 +194,6 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun): ...@@ -189,8 +194,6 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
# Run the test. # Run the test.
output = self._delegate.RunWithFlags(device, '--gtest_filter=%s' % test, output = self._delegate.RunWithFlags(device, '--gtest_filter=%s' % test,
timeout=900, retries=0) timeout=900, retries=0)
for s in self._servers[str(device)]:
s.Reset()
self._delegate.Clear(device) self._delegate.Clear(device)
# Parse the output. # Parse the output.
...@@ -200,9 +203,5 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun): ...@@ -200,9 +203,5 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
#override #override
def TearDown(self): def TearDown(self):
def individual_device_tear_down(dev): pass
for s in self._servers[str(dev)]:
s.TearDown()
self._env.parallel_devices.pMap(individual_device_tear_down)
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
'<(DEPTH)/base/base.gyp:base_java', '<(DEPTH)/base/base.gyp:base_java',
'<(DEPTH)/build/android/pylib/device/commands/commands.gyp:chromium_commands', '<(DEPTH)/build/android/pylib/device/commands/commands.gyp:chromium_commands',
'<(DEPTH)/build/android/pylib/remote/device/dummy/dummy.gyp:remote_device_dummy_apk', '<(DEPTH)/build/android/pylib/remote/device/dummy/dummy.gyp:remote_device_dummy_apk',
'<(DEPTH)/net/net.gyp:net_java_test_support',
'<(DEPTH)/tools/android/android_tools.gyp:android_tools', '<(DEPTH)/tools/android/android_tools.gyp:android_tools',
], ],
'conditions': [ 'conditions': [
......
...@@ -1490,6 +1490,7 @@ template("unittest_apk") { ...@@ -1490,6 +1490,7 @@ template("unittest_apk") {
deps = [ deps = [
"//base:base_java", "//base:base_java",
"//build/android/pylib/remote/device/dummy:remote_device_dummy_apk", "//build/android/pylib/remote/device/dummy:remote_device_dummy_apk",
"//net/android:net_java_test_support",
] ]
if (defined(invoker.deps)) { if (defined(invoker.deps)) {
deps += invoker.deps deps += invoker.deps
......
...@@ -3043,6 +3043,7 @@ ...@@ -3043,6 +3043,7 @@
'dependencies': [ 'dependencies': [
'chrome_java', 'chrome_java',
'../content/content_shell_and_tests.gyp:content_java_test_support', '../content/content_shell_and_tests.gyp:content_java_test_support',
'../net/net.gyp:net_java_test_support',
'../sync/sync.gyp:sync_java', '../sync/sync.gyp:sync_java',
'../sync/sync.gyp:sync_java_test_support', '../sync/sync.gyp:sync_java_test_support',
], ],
......
...@@ -16,6 +16,7 @@ android_library("chrome_java_test_support") { ...@@ -16,6 +16,7 @@ android_library("chrome_java_test_support") {
"//components/invalidation:java", "//components/invalidation:java",
"//content/public/android:content_java", "//content/public/android:content_java",
"//content/public/test/android:content_java_test_support", "//content/public/test/android:content_java_test_support",
"//net/android:net_java_test_support",
"//sync/android:sync_java", "//sync/android:sync_java",
"//sync:sync_java_test_support", "//sync:sync_java_test_support",
"//ui/android:ui_java", "//ui/android:ui_java",
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
package org.chromium.chrome.test; package org.chromium.chrome.test;
import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.util.Log; import android.util.Log;
...@@ -16,9 +17,7 @@ import org.apache.http.HttpStatus; ...@@ -16,9 +17,7 @@ import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion; import org.apache.http.HttpVersion;
import org.apache.http.RequestLine; import org.apache.http.RequestLine;
import org.apache.http.StatusLine; import org.apache.http.StatusLine;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.FileEntity; import org.apache.http.entity.FileEntity;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.message.BasicHttpResponse; import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine; import org.apache.http.message.BasicStatusLine;
import org.apache.http.params.BasicHttpParams; import org.apache.http.params.BasicHttpParams;
...@@ -26,15 +25,14 @@ import org.apache.http.params.CoreProtocolPNames; ...@@ -26,15 +25,14 @@ import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams; import org.apache.http.params.HttpParams;
import org.chromium.base.test.BaseInstrumentationTestRunner; import org.chromium.base.test.BaseInstrumentationTestRunner;
import org.chromium.net.test.BaseHttpTestServer;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
/** /**
...@@ -74,38 +72,30 @@ public class ChromeInstrumentationTestRunner extends BaseInstrumentationTestRunn ...@@ -74,38 +72,30 @@ public class ChromeInstrumentationTestRunner extends BaseInstrumentationTestRunn
@Override @Override
public void onStart() { public void onStart() {
if (mRunTestHttpServer) startTestHttpServer(); if (mRunTestHttpServer) {
try {
startTestHttpServer();
} catch (IOException e) {
Log.e(TAG, "Failed to start HTTP test server", e);
finish(Activity.RESULT_CANCELED, null);
}
}
super.onStart(); super.onStart();
} }
private void startTestHttpServer() { private void startTestHttpServer() throws IOException {
Object startLock = new Object(); mHttpServer = new TestHttpServer(Environment.getExternalStorageDirectory(), 8000);
mHttpServer = new TestHttpServer(Environment.getExternalStorageDirectory(), 8000,
startLock);
mHttpServerThread = new Thread(mHttpServer); mHttpServerThread = new Thread(mHttpServer);
mHttpServerThread.start(); mHttpServerThread.start();
synchronized (startLock) {
try {
while (!mHttpServer.hasStarted()) {
startLock.wait();
}
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while starting test http server: " + e.toString());
}
}
} }
private static class TestHttpServer implements Runnable { private static class TestHttpServer extends BaseHttpTestServer {
private static final String TAG = "ChromeInstrumentationTestRunner.TestHttpServer"; private static final String TAG = "ChromeInstrumentationTestRunner.TestHttpServer";
private AtomicBoolean mHasStarted;
private AtomicBoolean mKeepRunning;
private int mPort;
private final File mRootDirectory; private final File mRootDirectory;
private final String mRootPath; private final String mRootPath;
private ServerSocket mServerSocket;
private Object mStartLock;
private static final int ACCEPT_TIMEOUT_MS = 5000;
private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream"; private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
private static final Map<String, String> EXTENSION_CONTENT_TYPE_MAP; private static final Map<String, String> EXTENSION_CONTENT_TYPE_MAP;
static { static {
...@@ -132,62 +122,26 @@ public class ChromeInstrumentationTestRunner extends BaseInstrumentationTestRunn ...@@ -132,62 +122,26 @@ public class ChromeInstrumentationTestRunner extends BaseInstrumentationTestRunn
EXTENSION_CONTENT_TYPE_MAP = Collections.unmodifiableMap(m); EXTENSION_CONTENT_TYPE_MAP = Collections.unmodifiableMap(m);
} }
public TestHttpServer(File rootDirectory, int port, Object startLock) { public TestHttpServer(File rootDirectory, int port) throws IOException {
mHasStarted = new AtomicBoolean(false); super(port, ACCEPT_TIMEOUT_MS);
mKeepRunning = new AtomicBoolean(true);
mPort = port;
mRootDirectory = rootDirectory; mRootDirectory = rootDirectory;
mRootPath = mRootDirectory.getAbsolutePath(); mRootPath = mRootDirectory.getAbsolutePath();
mServerSocket = null;
mStartLock = startLock;
} }
@Override @Override
public void run() { protected boolean validateSocket(Socket sock) {
synchronized (mStartLock) { return !sock.getInetAddress().isLoopbackAddress();
try { }
mServerSocket = new ServerSocket(mPort);
} catch (IOException e) {
Log.e(TAG, "Could not open server socket on " + Integer.toString(mPort)
+ ": " + e.toString());
return;
} finally {
mHasStarted.set(true);
mStartLock.notify();
}
}
@Override
protected HttpParams getConnectionParams() {
HttpParams httpParams = new BasicHttpParams(); HttpParams httpParams = new BasicHttpParams();
httpParams.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_0); httpParams.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_0);
return httpParams;
while (mKeepRunning.get()) {
try {
Socket sock = mServerSocket.accept();
if (!sock.getInetAddress().isLoopbackAddress()) {
Log.w(TAG, "Remote connections forbidden.");
sock.close();
continue;
}
DefaultHttpServerConnection conn = new DefaultHttpServerConnection();
conn.bind(sock, httpParams);
HttpRequest request = conn.receiveRequestHeader();
HttpResponse response = generateResponse(request);
conn.sendResponseHeader(response);
conn.sendResponseEntity(response);
conn.close();
sock.close();
} catch (IOException e) {
Log.e(TAG, "Error while handling incoming connection: " + e.toString());
} catch (HttpException e) {
Log.e(TAG, "Error while handling HTTP request: " + e.toString());
}
}
} }
private HttpResponse generateResponse(HttpRequest request) { @Override
protected HttpResponse handleGet(HttpRequest request) throws HttpException {
RequestLine requestLine = request.getRequestLine(); RequestLine requestLine = request.getRequestLine();
String requestPath = requestLine.getUri(); String requestPath = requestLine.getUri();
...@@ -200,11 +154,7 @@ public class ChromeInstrumentationTestRunner extends BaseInstrumentationTestRunn ...@@ -200,11 +154,7 @@ public class ChromeInstrumentationTestRunner extends BaseInstrumentationTestRunn
int status = HttpStatus.SC_INTERNAL_SERVER_ERROR; int status = HttpStatus.SC_INTERNAL_SERVER_ERROR;
String reason = ""; String reason = "";
HttpEntity entity = null; HttpEntity entity = null;
if (!HttpGet.METHOD_NAME.equals(requestLine.getMethod())) { if (!requestedPath.startsWith(mRootPath)) {
Log.w(TAG, "Client made request using unsupported method: "
+ requestLine.getMethod());
status = HttpStatus.SC_METHOD_NOT_ALLOWED;
} else if (!requestedPath.startsWith(mRootPath)) {
Log.w(TAG, "Client tried to request something outside of " + mRootPath + ": " Log.w(TAG, "Client tried to request something outside of " + mRootPath + ": "
+ requestedPath); + requestedPath);
status = HttpStatus.SC_FORBIDDEN; status = HttpStatus.SC_FORBIDDEN;
...@@ -239,14 +189,6 @@ public class ChromeInstrumentationTestRunner extends BaseInstrumentationTestRunn ...@@ -239,14 +189,6 @@ public class ChromeInstrumentationTestRunner extends BaseInstrumentationTestRunn
} }
return response; return response;
} }
public boolean hasStarted() {
return mHasStarted.get();
}
public void stopServer() {
mKeepRunning.set(false);
}
} }
@Override @Override
...@@ -256,7 +198,7 @@ public class ChromeInstrumentationTestRunner extends BaseInstrumentationTestRunn ...@@ -256,7 +198,7 @@ public class ChromeInstrumentationTestRunner extends BaseInstrumentationTestRunn
} }
private void stopTestHttpServer() { private void stopTestHttpServer() {
mHttpServer.stopServer(); mHttpServer.stop();
try { try {
mHttpServerThread.join(); mHttpServerThread.join();
} catch (InterruptedException e) { } catch (InterruptedException e) {
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.test;
import android.util.Log;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import java.io.IOException;
import java.net.Socket;
/** A base class for simple HTTP test servers. */
public abstract class BaseHttpTestServer extends BaseTcpTestServer {
private static final String TAG = "BaseHttpTestServer";
/**
* Create an HTTP test server on the given port.
*
* @param serverPort The port to listen on for incoming HTTP connections.
* @param acceptTimeoutMs The timeout for calls to ServerSocket.accept(), in milliseconds.
* @throws IOException If the server port can't be bound.
*/
public BaseHttpTestServer(int serverPort, int acceptTimeoutMs) throws IOException {
super(serverPort, acceptTimeoutMs);
}
/**
* Handle an incoming connection on |sock|.
*
* This will bind the socket, receive the request header (and entity, if applicable),
* dispatch the request to a handler based on the request method, and send the response
* generated by the handler.
*
* @param sock The socket for the incoming connection.
* @throws IOException If an error occurs while reading from or writing to the socket.
*/
@Override
protected final void handle(Socket sock) throws IOException {
HttpParams httpParams = getConnectionParams();
DefaultHttpServerConnection conn = new DefaultHttpServerConnection();
conn.bind(sock, httpParams);
try {
HttpRequest request = conn.receiveRequestHeader();
if (request instanceof HttpEntityEnclosingRequest) {
conn.receiveRequestEntity((HttpEntityEnclosingRequest) request);
}
HttpResponse response = null;
switch (request.getRequestLine().getMethod()) {
case HttpGet.METHOD_NAME:
response = handleGet(request);
break;
case HttpPost.METHOD_NAME:
response = handlePost((HttpEntityEnclosingRequest) request);
break;
default:
response = handleUnsupported(request);
break;
}
conn.sendResponseHeader(response);
conn.sendResponseEntity(response);
} catch (HttpException e) {
Log.e(TAG, "Error while handling HTTP request", e);
} finally {
conn.close();
}
}
/**
* Returns the parameters used to establish the HTTP connection.
*
* The default implementation returns a default BasicHttpParams instance.
*
* @return The HttpParams to use to establish the HTTP connection.
*/
protected HttpParams getConnectionParams() {
return new BasicHttpParams();
}
/**
* Handle a GET request.
*
* The default implementation returns a 405. Override this function if you want to support
* GET.
*
* @param request The GET request to handle.
* @return The response to the GET request.
*/
protected HttpResponse handleGet(HttpRequest request) throws HttpException {
return handleUnsupported(request);
}
/**
* Handle a POST request.
*
* The default implementation returns a 405. Override this function if you want to support
* POST.
*
* @param request The POST request to handle.
* @return The response to the POST request.
*/
protected HttpResponse handlePost(HttpEntityEnclosingRequest request) throws HttpException {
return handleUnsupported(request);
}
/** Handle an unsupported HTTP request.
*
* @param request The unsupported HTTP request.
* @return A 405 Method Not Allowed response.
*/
protected HttpResponse handleUnsupported(HttpRequest request) {
return new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_METHOD_NOT_ALLOWED, "");
}
}
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.test;
import android.util.Log;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
/** A base class for simple TCP test servers. */
public abstract class BaseTcpTestServer extends BaseTestServer {
private static final String TAG = "BaseTcpTestServer";
private ServerSocket mServerSocket;
/**
* Creates a TCP test server on the given port.
*
* @param serverPort The port to listen on for incoming TCP connections.
* @param acceptTimeoutMs The timeout for calls to ServerSocket.accept(), in milliseconds.
* @throws IOException If the server port can't be bound.
*/
public BaseTcpTestServer(int serverPort, int acceptTimeoutMs) throws IOException {
mServerSocket = new ServerSocket(serverPort);
mServerSocket.setSoTimeout(acceptTimeoutMs);
}
/** Returns the port on which this server is listening for connections. */
@Override
public int getServerPort() {
return mServerSocket.getLocalPort();
}
/** Waits for and handles an incoming request. */
protected final void accept() {
try {
Socket sock = mServerSocket.accept();
if (!validateSocket(sock)) {
Log.e(TAG, "Socket failed validation.");
sock.close();
return;
}
handle(sock);
} catch (SocketTimeoutException e) {
Log.i(TAG, "Timed out waiting for incoming connection.");
} catch (IOException e) {
Log.e(TAG, "Error while handling incoming connection", e);
}
}
/**
* Returns whether the connection open on the given socket should be handled.
*
* @param sock The socket to validate.
*/
protected abstract boolean validateSocket(Socket sock);
/**
* Handles the connection open on the given socket.
*
* @param sock The socket to handle.
* @throws IOException If an error occurs while reading from or writing to the socket.
*/
protected abstract void handle(Socket sock) throws IOException;
}
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.test;
import java.util.concurrent.atomic.AtomicBoolean;
/** A base class for simple test servers. */
public abstract class BaseTestServer implements Runnable {
private AtomicBoolean mKeepRunning;
/** Creates a test server. */
public BaseTestServer() {
mKeepRunning = new AtomicBoolean(true);
}
/** Accepts incoming connections until stopped via stop(). */
public void run() {
mKeepRunning.set(true);
while (mKeepRunning.get()) {
accept();
}
}
/** Waits for and handles an incoming request. */
protected abstract void accept();
/** Returns the port on which this server is listening for connections. */
public abstract int getServerPort();
/** Stops the server. */
public void stop() {
mKeepRunning.set(false);
}
}
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.test;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
/** Creates instances of BaseTestServer.
*
* This will eventually create instances of:
* - FtpTestServer
* - HttpTestServer
* - TcpEchoTestServer
* - UdpEchoTestServer
* - WebsocketTestServer
* depending on the parameters passed to the constructor.
*/
class TestServerBuilder {
private static final String TAG = "TestServerBuilder";
private static final Pattern SWITCH_PREFIX_RE = Pattern.compile("--");
private static final Pattern SWITCH_VALUE_SEPARATOR_RE = Pattern.compile("=");
private static final Set<String> OCSP_RESPONSES;
private static final Set<String> SSL_BULK_CIPHERS;
private static final Set<String> SSL_CLIENT_CERT_TYPES;
private static final Set<String> SSL_KEY_EXCHANGE_ALGORITHMS;
private static final Set<String> TLS_INTOLERANCE_TYPES;
static {
HashSet<String> ocspResponses = new HashSet<String>();
ocspResponses.add("ok");
ocspResponses.add("revoked");
ocspResponses.add("invalid");
OCSP_RESPONSES = Collections.unmodifiableSet(ocspResponses);
HashSet<String> sslBulkCiphers = new HashSet<String>();
sslBulkCiphers.add("aes256");
sslBulkCiphers.add("aes128");
sslBulkCiphers.add("3des");
sslBulkCiphers.add("rc4");
SSL_BULK_CIPHERS = Collections.unmodifiableSet(sslBulkCiphers);
HashSet<String> sslClientCertTypes = new HashSet<String>();
sslClientCertTypes.add("rsa_sign");
sslClientCertTypes.add("dss_sign");
sslClientCertTypes.add("ecdsa_sign");
SSL_CLIENT_CERT_TYPES = Collections.unmodifiableSet(sslClientCertTypes);
HashSet<String> sslKeyExchangeAlgorithms = new HashSet<String>();
sslKeyExchangeAlgorithms.add("rsa");
sslKeyExchangeAlgorithms.add("dhe_rsa");
SSL_KEY_EXCHANGE_ALGORITHMS = Collections.unmodifiableSet(sslKeyExchangeAlgorithms);
HashSet<String> tlsIntoleranceTypes = new HashSet<String>();
tlsIntoleranceTypes.add("alert");
tlsIntoleranceTypes.add("close");
tlsIntoleranceTypes.add("reset");
TLS_INTOLERANCE_TYPES = Collections.unmodifiableSet(tlsIntoleranceTypes);
}
private enum TlsIntolerant {
MIN(0),
TOLERATE_ALL(0),
ABORT_ALL(1),
ABORT_1_1_AND_ABOVE(2),
ABORT_1_2_AND_ABOVE(3),
MAX(3);
private TlsIntolerant(int value) {
mValue = value;
}
public int value() {
return mValue;
}
private final int mValue;
}
private enum ServerType {
HTTP,
FTP,
TCP_ECHO,
UDP_ECHO,
BASIC_AUTH_PROXY,
WEBSOCKET,
}
// The path to the file containing the certificate and private key that the server should use,
// in PEM format.
private String mCertAndKeyFile;
// Directory from which the server should read data files.
private String mDataDir;
// If true, the server should disable the TLS session cache. Defaults to false.
private boolean mDisableSessionCache;
// Root URL for files served. Defaults to "/files/"
private String mFileRootUrl = "/files/";
// Hostname or IP the server should listen on and accept connections from.
// Defaults to "127.0.0.1"
private String mHost = "127.0.0.1";
// If true, the server should use HTTPS.
private boolean mHttps;
// If true, log to the logcat.
private boolean mLogToConsole;
// Type of OCSP response generated. Defaults to "ok".
private String mOcsp = "ok";
// Port to be used by the server.
private int mPort;
// Type of server to build.
private ServerType mServerType = ServerType.HTTP;
// The bulk encryption algorithms that should be accepted by the server.
// Defaults to "aes256", "aes128", "3des", "rc4"
private List<String> mSslBulkCiphers;
// If true, the server should require SSL client authentication on every connection.
// Defaults to false.
private boolean mSslClientAuth;
// The CA names that should be included in the client certificate requests.
private List<String> mSslClientCas;
// The certificate_types that should be included in the client certificate request.
// Defaults to "rsa_sign"
private List<String> mSslClientCertTypes;
// The key exchange algorithms that should be accepted by the server.
// Defaults to "rsa", "dhe_rsa"
private List<String> mSslKeyExchanges;
/** Create a TestServerBuilder.
*
* @param json The server configuration. Determines the type of server spawned and the specific
* configuration thereof.
*/
public TestServerBuilder(JSONObject json) throws JSONException {
mSslBulkCiphers = new ArrayList<String>(SSL_BULK_CIPHERS);
mSslClientCas = new ArrayList<String>();
mSslClientCertTypes = new ArrayList<String>();
mSslClientCertTypes.add("rsa_sign");
mSslKeyExchanges = new ArrayList<String>(SSL_KEY_EXCHANGE_ALGORITHMS);
parse(json);
}
/** Create a BaseTestServer.
*
* @return An instance of BaseTestServer.
*/
public BaseTestServer build() {
Log.i(TAG, "building with: " + toString());
// TODO(jbudorick): Implement this in a subsequent CL.
throw new UnsupportedOperationException(
"TestServerBuilder.build() hasn't been implemented yet.");
}
private void parse(JSONObject json) throws JSONException {
Iterator<String> keyIter = json.keys();
while (keyIter.hasNext()) {
String key = keyIter.next();
Log.i(TAG, "Received key: " + key);
switch (key) {
case "cert-and-key-file":
mCertAndKeyFile = json.getString(key);
break;
case "data-dir":
mDataDir = json.getString(key);
break;
case "disable-session-cache":
mDisableSessionCache = true;
break;
case "host":
mHost = json.getString(key);
break;
case "https":
mHttps = true;
break;
case "log-to-console":
mLogToConsole = true;
break;
case "port":
mPort = json.getInt(key);
break;
case "server-type":
parseServerType(json.getString(key));
break;
case "ssl-client-auth":
mSslClientAuth = true;
break;
case "ssl-client-ca":
mSslClientCas = jsonArrayAsStringList(json.getJSONArray(key));
break;
case "ssl-key-exchange":
mSslKeyExchanges = jsonArrayAsStringList(json.getJSONArray(key));
if (SSL_KEY_EXCHANGE_ALGORITHMS.containsAll(mSslKeyExchanges)) {
mSslKeyExchanges.removeAll(SSL_KEY_EXCHANGE_ALGORITHMS);
throw new JSONException("invalid values provided for ssl-key-exchange: "
+ mSslKeyExchanges.toString());
}
break;
default:
Log.e(TAG, "Unrecognized command-line flag: " + key);
break;
}
}
}
private void parseServerType(String serverType) throws JSONException {
switch (serverType) {
case "http":
mServerType = ServerType.HTTP;
break;
case "ftp":
mServerType = ServerType.FTP;
break;
case "tcp-echo":
mServerType = ServerType.TCP_ECHO;
break;
case "udp-echo":
mServerType = ServerType.UDP_ECHO;
break;
case "basic-auth-proxy":
mServerType = ServerType.BASIC_AUTH_PROXY;
break;
case "websocket":
mServerType = ServerType.WEBSOCKET;
break;
default:
throw new JSONException("Unrecognized server-type value: \"" + serverType + "\"");
}
}
private static List<String> jsonArrayAsStringList(JSONArray jsonArray) throws JSONException {
List<String> result = new ArrayList<String>(jsonArray.length());
for (int i = 0; i < jsonArray.length(); ++i) {
result.set(i, jsonArray.getString(i));
}
return result;
}
public String toString() {
StringBuilder resultBuilder = new StringBuilder("{");
resultBuilder.append(formatString("mCertAndKeyFile", mCertAndKeyFile));
resultBuilder.append(formatString("mDataDir", mDataDir));
resultBuilder.append(formatBoolean("mDisableSessionCache", mDisableSessionCache));
resultBuilder.append(formatString("mFileRootUrl", mFileRootUrl));
resultBuilder.append(formatString("mHost", mHost));
resultBuilder.append(formatBoolean("mHttps", mHttps));
resultBuilder.append(formatBoolean("mLogToConsole", mLogToConsole));
resultBuilder.append(formatString("mOcsp", mOcsp));
resultBuilder.append(formatInt("mPort", mPort));
resultBuilder.append(formatServerType("mServerType", mServerType));
resultBuilder.append(formatList("mSslBulkCiphers", mSslBulkCiphers));
resultBuilder.append(formatBoolean("mSslClientAuth", mSslClientAuth));
resultBuilder.append(formatList("mSslClientCas", mSslClientCas));
resultBuilder.append(formatList("mSslClientCertTypes", mSslClientCertTypes));
resultBuilder.append(formatList("mSslKeyExchanges", mSslKeyExchanges));
resultBuilder.append("}");
return resultBuilder.toString();
}
private String formatString(String label, String value) {
return "\"" + label + "\": \"" + formatString(value) + "\",";
}
private String formatString(String value) {
return value != null ? value : "null";
}
private String formatInt(String label, int value) {
return "\"" + label + "\": \"" + Integer.toString(value) + "\",";
}
private String formatBoolean(String label, boolean value) {
return "\"" + label + "\": \"" + Boolean.toString(value) + "\",";
}
private String formatList(String label, List<String> value) {
StringBuilder resultBuilder = new StringBuilder("\"");
resultBuilder.append(label).append("\": [");
for (String s : value) {
resultBuilder.append("\"").append(formatString(s)).append("\",");
}
resultBuilder.append("],");
return resultBuilder.toString();
}
private String formatServerType(String label, ServerType serverType) {
String result = "\"mServerType\": \"";
switch (serverType) {
case HTTP:
result += "HTTP";
break;
case FTP:
result += "FTP";
break;
case TCP_ECHO:
result += "TCP_ECHO";
break;
case UDP_ECHO:
result += "UDP_ECHO";
break;
case BASIC_AUTH_PROXY:
result += "BASIC_AUTH_PROXY";
break;
case WEBSOCKET:
result += "WEBSOCKET";
break;
default:
result += "???";
break;
}
result += "\",";
return result;
}
}
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.test;
import android.util.Log;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.RequestLine;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicHttpResponse;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
/**
* A server that spawns test servers based on request JSONs.
*
* This communicates with net::SpawnerCommunicator according to the protocol specified in
* //net/test/spawned_test_server/spawner_communicator.h. It spawns test servers on the device.
*/
public class TestServerSpawner extends BaseHttpTestServer {
private static final String TAG = "TestServerSpawner";
private static final int ARBITRARY_MAX_JSON_SIZE = 65536;
private static final String COMMAND_KILL = "/kill";
private static final String COMMAND_PING = "/ping";
private static final String COMMAND_START = "/start";
private BaseTestServer mTestServer;
private Thread mTestServerThread;
/**
* Creates a test server spawner on the given port.
*
* @param serverPort The port to listen on for incoming connections.
* @param acceptTimeoutMs The timeout for calls to ServerSocket.accept(), in milliseconds.
* @throws IOException If the server port can't be bound.
*/
public TestServerSpawner(int serverPort, int acceptTimeoutMs) throws IOException {
super(serverPort, acceptTimeoutMs);
mTestServer = null;
mTestServerThread = null;
}
/**
* Returns true if the socket is coming from a local address.
*
* @param sock The socket to validate.
* @return True if the remote endpoint is local, false otherwise.
*/
@Override
protected boolean validateSocket(Socket sock) {
return sock.getInetAddress().isLoopbackAddress();
}
/**
* Handles a GET request.
*
* This handles the /kill and /ping commands. It returns 403s for anything else.
*
* @param request The GET request to handle.
* @return The response to |request|.
*/
@Override
protected HttpResponse handleGet(HttpRequest request) {
RequestLine requestLine = request.getRequestLine();
String uri = requestLine.getUri();
int status = HttpStatus.SC_INTERNAL_SERVER_ERROR;
String reason = "";
HttpEntity entity = null;
try {
// TODO(jbudorick): Refactor how this communicates with RemoteTestServer and
// SpawnerCommunicator (or maybe how we spawn and manage test servers on android
// entirely) once tests are no longer using chrome_test_server_spawner.py.
// The server startup and shutdown processes should be done in code (e.g. with events)
// rather than over HTTP. crbug/452596
if (COMMAND_KILL.equals(uri)) {
Log.i(TAG, "Received GET /kill request.");
if (mTestServer != null) {
mTestServer.stop();
mTestServerThread.join();
status = HttpStatus.SC_OK;
entity = new StringEntity("killed");
mTestServer = null;
} else {
status = HttpStatus.SC_BAD_REQUEST;
reason = "Test server does not exist.";
}
} else if (COMMAND_PING.equals(uri)) {
Log.i(TAG, "Received GET /ping request.");
status = HttpStatus.SC_OK;
entity = new StringEntity("ready");
} else {
status = HttpStatus.SC_FORBIDDEN;
}
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while joining test server thread: " + e.toString());
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "Unsupported encoding while writing response entity: " + e.toString());
entity = null;
}
BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_0, status, reason);
response.setEntity(entity);
return response;
}
/**
* Handles a POST request.
*
* This handles the /start command. It returns 403s for anything else.
*
* @param request The POST request to handle.
* @return The response to |request|.
*/
@Override
protected HttpResponse handlePost(HttpEntityEnclosingRequest request) throws HttpException {
RequestLine requestLine = request.getRequestLine();
String uri = requestLine.getUri();
int status = HttpStatus.SC_INTERNAL_SERVER_ERROR;
String reason = "";
HttpEntity responseEntity = null;
if (COMMAND_START.equals(uri)) {
Log.i(TAG, "Received POST /start request.");
BufferedReader entityReader = null;
try {
HttpEntity requestEntity = request.getEntity();
if (requestEntity.getContentLength() > ARBITRARY_MAX_JSON_SIZE) {
throw new HttpException("Request JSON too long ("
+ Long.toString(requestEntity.getContentLength()) + " bytes)");
}
entityReader = new BufferedReader(
new InputStreamReader(new BufferedInputStream(requestEntity.getContent())));
StringBuilder rawJson = new StringBuilder();
for (String line = entityReader.readLine(); line != null;
line = entityReader.readLine()) {
rawJson.append(line);
}
mTestServer = new TestServerBuilder(new JSONObject(rawJson.toString())).build();
mTestServerThread = new Thread(mTestServer);
mTestServerThread.start();
status = HttpStatus.SC_OK;
JSONObject entityJson = new JSONObject();
entityJson.put("port", mTestServer.getServerPort());
entityJson.put("message", "started");
responseEntity = new StringEntity(entityJson.toString());
} catch (UnsupportedOperationException e) {
// TODO(jbudorick): Remove this catch block once TestServerFactory.createTestServer
// is fully implemented.
throw new HttpException("Error creating test server", e);
} catch (JSONException e) {
throw new HttpException("Error handling JSON", e);
} catch (UnsupportedEncodingException e) {
throw new HttpException("Error generating response", e);
} catch (IOException e) {
throw new HttpException("Error while reading HTTP entity", e);
} finally {
try {
if (entityReader != null) entityReader.close();
} catch (IOException e) {
Log.e(TAG, "Unable to close entity input stream", e);
}
}
} else {
status = HttpStatus.SC_FORBIDDEN;
}
BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_0, status, reason);
response.setEntity(responseEntity);
return response;
}
}
...@@ -12,13 +12,17 @@ import android.os.Bundle; ...@@ -12,13 +12,17 @@ import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.util.Log; import android.util.Log;
import org.chromium.net.test.TestServerSpawner;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
...@@ -32,9 +36,15 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation { ...@@ -32,9 +36,15 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation {
public static final String EXTRA_ONLY_OUTPUT_FAILURES = public static final String EXTRA_ONLY_OUTPUT_FAILURES =
"org.chromium.native_test.ChromeNativeTestInstrumentationTestRunner." "org.chromium.native_test.ChromeNativeTestInstrumentationTestRunner."
+ "OnlyOutputFailures"; + "OnlyOutputFailures";
public static final String EXTRA_ENABLE_TEST_SERVER_SPAWNER =
"org.chromium.native_test.ChromeNativeTestInstrumentationTestRunner."
+ "EnableTestServerSpawner";
private static final String TAG = "ChromeNativeTestInstrumentationTestRunner"; private static final String TAG = "ChromeNativeTestInstrumentationTestRunner";
private static final int ACCEPT_TIMEOUT_MS = 5000;
private static final Pattern RE_TEST_OUTPUT = Pattern.compile("\\[ *([^ ]*) *\\] ?([^ ]+) .*"); private static final Pattern RE_TEST_OUTPUT = Pattern.compile("\\[ *([^ ]*) *\\] ?([^ ]+) .*");
private static final int SERVER_SPAWNER_PORT = 0;
private static interface ResultsBundleGenerator { private static interface ResultsBundleGenerator {
public Bundle generate(Map<String, TestResult> rawResults); public Bundle generate(Map<String, TestResult> rawResults);
...@@ -47,6 +57,10 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation { ...@@ -47,6 +57,10 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation {
private ResultsBundleGenerator mBundleGenerator; private ResultsBundleGenerator mBundleGenerator;
private boolean mOnlyOutputFailures; private boolean mOnlyOutputFailures;
private boolean mEnableTestServerSpawner;
private TestServerSpawner mTestServerSpawner;
private Thread mTestServerSpawnerThread;
@Override @Override
public void onCreate(Bundle arguments) { public void onCreate(Bundle arguments) {
mCommandLineFile = arguments.getString(ChromeNativeTestActivity.EXTRA_COMMAND_LINE_FILE); mCommandLineFile = arguments.getString(ChromeNativeTestActivity.EXTRA_COMMAND_LINE_FILE);
...@@ -63,16 +77,55 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation { ...@@ -63,16 +77,55 @@ public class ChromeNativeTestInstrumentationTestRunner extends Instrumentation {
mLogBundle = new Bundle(); mLogBundle = new Bundle();
mBundleGenerator = new RobotiumBundleGenerator(); mBundleGenerator = new RobotiumBundleGenerator();
mOnlyOutputFailures = arguments.containsKey(EXTRA_ONLY_OUTPUT_FAILURES); mOnlyOutputFailures = arguments.containsKey(EXTRA_ONLY_OUTPUT_FAILURES);
mEnableTestServerSpawner = arguments.containsKey(EXTRA_ENABLE_TEST_SERVER_SPAWNER);
mTestServerSpawner = null;
mTestServerSpawnerThread = null;
start(); start();
} }
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
setUp();
Bundle results = runTests(); Bundle results = runTests();
tearDown();
finish(Activity.RESULT_OK, results); finish(Activity.RESULT_OK, results);
} }
private void setUp() {
if (mEnableTestServerSpawner) {
Log.i(TAG, "Test server spawner enabled.");
try {
mTestServerSpawner = new TestServerSpawner(SERVER_SPAWNER_PORT, ACCEPT_TIMEOUT_MS);
File portFile = new File(
Environment.getExternalStorageDirectory(), "net-test-server-ports");
OutputStreamWriter writer =
new OutputStreamWriter(new FileOutputStream(portFile));
writer.write(Integer.toString(mTestServerSpawner.getServerPort()) + ":0");
writer.close();
mTestServerSpawnerThread = new Thread(mTestServerSpawner);
mTestServerSpawnerThread.start();
} catch (IOException e) {
Log.e(TAG, "Error creating TestServerSpawner: " + e.toString());
}
}
}
private void tearDown() {
if (mTestServerSpawnerThread != null) {
try {
mTestServerSpawner.stop();
mTestServerSpawnerThread.join();
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while shutting down test server spawner: " + e.toString());
}
}
}
/** Runs the tests in the ChromeNativeTestActivity and returns a Bundle containing the results. /** Runs the tests in the ChromeNativeTestActivity and returns a Bundle containing the results.
*/ */
private Bundle runTests() { private Bundle runTests() {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment