This is the mail archive of the
mauve-patches@sourceware.org
mailing list for the Mauve project.
Re: RFC: HTTPTestServer
Hi David,
David Daney wrote:
[...]
>
> Not withstanding all of that, I think it is an excellent start to a very
> much needed capability. Just because it is not exactly what I would
> have done does not mean that it is not good and useful.
>
> My call: You decide. Commit it in as is, or keep hacking and commit
> later.
I will commit it now to have the tests available. However you are certainly
right that its nothing extensible and could be improved a lot. But I currently
have no time to do this further. If anyone in the future comes up with
something modular I will happily convert the tests to use that.
Committed as follows:
2006-03-06 Wolfgang Baer <WBaer@gmx.de>
* gnu/testlet/java/net/HttpURLConnection/TestHttpServer.java: New file.
* gnu/testlet/java/net/HttpURLConnection/responseCodeTest.java:
New test using TestHttpServer.java.
* gnu/testlet/java/net/HttpURLConnection/responseHeadersTest.java:
Likewise.
Wolfgang
//Tags: not-a-test
//Copyright (C) 2006 Free Software Foundation, Inc.
//Written by Wolfgang Baer (WBaer@gmx.de)
//This file is part of Mauve.
//Mauve is free software; you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation; either version 2, or (at your option)
//any later version.
//Mauve is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//GNU General Public License for more details.
//You should have received a copy of the GNU General Public License
//along with Mauve; see the file COPYING. If not, write to
//the Free Software Foundation, 51 Franklin Street, Fifth Floor,
//Boston, MA, 02110-1301 USA.
package gnu.testlet.java.net.HttpURLConnection;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
* A HTTP server for testing purpose only. The server can
* be started on a given port and the response headers and
* response body to be returned set. This way one can test
* arbritrary testcases for the http client side library.
*
* @see gnu.testlet.java.net.HttpURLConnection.responseCodeTest
* @see gnu.testlet.java.net.HttpURLConnection.responseHeadersTest
*
* @author Wolfgang Baer (WBaer@gmx.de)
*/
public final class TestHttpServer implements Runnable
{
/**
* The interface to be implemented if checking
* of the serverside received headers and body
* is desired.
*/
interface CheckReceivedRequest
{
/**
* Will be called with a List of headers in
* the sequence received by the test server.
*
* @param headers List of headers
*/
public void checkHeaders(List headers);
/**
* Will be called with the body if one was
* received.
*
* @param body the body
*/
public void checkBody(byte[] body);
}
/**
* The actual request handler.
* It always returns what is set in the TestHttpServer
* for the response headers and response body.
*/
class TestHttpRequestHandler implements Runnable
{
Socket socket;
OutputStream output;
InputStream input;
public TestHttpRequestHandler(Socket socket) throws Exception
{
this.socket = socket;
output = socket.getOutputStream();
input = socket.getInputStream();
}
public void run()
{
try
{
// Read the whole request into a byte array
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int bytes = 0 ;
while ((bytes = input.read(b)) != -1)
{
buffer.write(b, 0, bytes);
if (bytes < 1024)
break;
}
byte[] request = buffer.toByteArray();
// Parse the request headers from the byte array
List headerList = new ArrayList();
ByteArrayOutputStream line;
int i = 0;
line = new ByteArrayOutputStream();
for (; i < request.length; i++)
{
if (request[i] != (byte) 0x0a) // LF
line.write(request[i]);
else
{
byte[] array = line.toByteArray();
if (array.length == 1) // the last is only a LF
break;
String headerLine = new String(array);
headerList.add(headerLine);
line = new ByteArrayOutputStream();
}
}
// Put the remaining bytes into the request body
byte[] body = new byte[(request.length - (i + 1))];
System.arraycopy(request, i + 1, body, 0, body.length);
// Check everything
if (check != null)
{
check.checkHeaders(headerList);
if (body.length > 0)
check.checkBody(body);
}
// Response writing
// write the response headers
output.write(responseHeader);
// write the body
if (responseBody != null)
output.write(responseBody);
// Clean up
output.close();
input.close();
socket.close();
}
catch (Exception e)
{
// ignore
}
}
}
int port;
byte[] responseHeader;
byte[] responseBody;
CheckReceivedRequest check;
boolean kill = false;
ServerSocket serverSocket;
/**
* Create a TestHttpServer on given port
* @param port the port to use.
*/
public TestHttpServer(int port)
{
this.port = port;
}
/**
* An object implementing the CheckReceivedRequest
* interface. The methods will be called to enable
* checks of the serverside received request.
*
* @param responseBody the byte[] of the body
*/
public void setCheckReceivedRequest(CheckReceivedRequest object)
{
this.check = object;
}
/**
* The bytes which should be sent as the response body.
* @param responseBody the byte[] of the body
*/
public void setResponseBody(byte[] responseBody)
{
this.responseBody = responseBody;
}
/**
* The bytes which should be sent as the response headers.
* @param responseHeaders the byte[] of the headers
*/
public void setResponseHeaders(byte[] responseHeaders)
{
this.responseHeader = responseHeaders;
}
/**
* This cleans up recources so more than one
* TestHttpServer can be used in one mauve run.
*/
public void killTestServer()
{
kill = true;
try
{
serverSocket.close();
}
catch (IOException e)
{
// ignore
}
}
/**
* Listens on the port and creates a Handler for
* incoming connections.
*/
public void run()
{
try
{
serverSocket = new ServerSocket(port);
while (! kill)
{
Socket socket = serverSocket.accept();
try
{
TestHttpRequestHandler request =
new TestHttpRequestHandler(socket);
Thread thread = new Thread(request);
thread.start();
}
catch (Exception e)
{
// ignore
}
}
}
catch (IOException e)
{
// ignore
}
}
}
//Tags: JDK1.1
//Uses: TestHttpServer
//Copyright (C) 2006 Free Software Foundation, Inc.
//Written by Wolfgang Baer (WBaer@gmx.de)
//This file is part of Mauve.
//Mauve is free software; you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation; either version 2, or (at your option)
//any later version.
//Mauve is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//GNU General Public License for more details.
//You should have received a copy of the GNU General Public License
//along with Mauve; see the file COPYING. If not, write to
//the Free Software Foundation, 51 Franklin Street, Fifth Floor,
//Boston, MA, 02110-1301 USA.
package gnu.testlet.java.net.HttpURLConnection;
import gnu.testlet.TestHarness;
import gnu.testlet.Testlet;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* Tests correct behaviour of getInputStream(), getErrorStream()
* for all error response codes.
*/
public class responseCodeTest implements Testlet
{
/**
* Starts an HTTP server on port 8080 and calls
* the test_ResponseCode for the error codes.
*/
public void test(TestHarness h)
{
TestHttpServer server = null;
try
{
server = new TestHttpServer(8080);
Thread thread = new Thread(server);
thread.start();
try
{
// SUN JDK needs some time to open sockets
Thread.sleep(100);
}
catch (InterruptedException e1)
{
}
for (int i=400; i < 418; i++)
test_ResponseCode(i, h, server);
for (int i=500; i < 506; i++)
test_ResponseCode(i, h, server);
}
finally
{
server.killTestServer();
}
}
public void test_ResponseCode(int responseCode,
TestHarness h, TestHttpServer server)
{
try
{
URL url = new URL("http://localhost:8080/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
h.checkPoint("Test " + responseCode + " response");
// construct what should be returned by the test server as headers
ByteArrayOutputStream headers = new ByteArrayOutputStream();
// status line (the responsecode encoded for the test)
headers.write(new String("HTTP/1.0 " + responseCode + " OK\r\n").getBytes());
headers.write("Server: TestServer\r\n\r\n".getBytes());
server.setResponseHeaders(headers.toByteArray());
// test the responsecode
int code = conn.getResponseCode();
h.check(code == responseCode);
// getInputStream should always throw an IOException
try
{
conn.getInputStream();
h.check(false);
}
catch (IOException e)
{
// for a 404/410 it must be a FNFE
if (responseCode == 404
|| responseCode == 410)
{
// Since JDK 1.5 as FNFE is thrown - so this will fail for 1.4
if (e instanceof FileNotFoundException)
h.check(true);
else
h.check(false);
}
else
h.check(true);
}
// the errorstream must be set always
InputStream error = conn.getErrorStream();
h.check(error != null);
conn.disconnect();
}
catch (IOException e)
{
h.debug("Unexpected IOException");
h.debug(e);
}
}
}
//Tags: JDK1.4
//Uses: TestHttpServer
//Copyright (C) 2006 Free Software Foundation, Inc.
//Written by Wolfgang Baer (WBaer@gmx.de)
//This file is part of Mauve.
//Mauve is free software; you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation; either version 2, or (at your option)
//any later version.
//Mauve is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//GNU General Public License for more details.
//You should have received a copy of the GNU General Public License
//along with Mauve; see the file COPYING. If not, write to
//the Free Software Foundation, 51 Franklin Street, Fifth Floor,
//Boston, MA, 02110-1301 USA.
package gnu.testlet.java.net.HttpURLConnection;
import gnu.testlet.TestHarness;
import gnu.testlet.Testlet;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Map;
public class responseHeadersTest implements Testlet
{
public void test(TestHarness harness)
{
TestHttpServer server = null;
try
{
server = new TestHttpServer(8080);
Thread thread = new Thread(server);
thread.start();
try
{
// SUN JDK needs some time to open sockets
Thread.sleep(100);
}
catch (InterruptedException e1)
{
}
test_MultiHeaders(harness, server);
test_LowerUpperCaseHeaders(harness, server);
}
finally
{
server.killTestServer();
}
}
public void test_MultiHeaders(TestHarness h, TestHttpServer server)
{
try
{
URL url = new URL("http://localhost:8080/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
// construct what should be returned by the test server as headers
ByteArrayOutputStream headers = new ByteArrayOutputStream();
headers.write("HTTP/1.0 200 OK\r\n".getBytes());
headers.write("Server: TestServer\r\n".getBytes());
headers.write("Key1: value, value2\r\n".getBytes());
// set the header a second time with different values
// these values must be prepended to key1
headers.write("Key1: value3\r\n".getBytes());
headers.write("IntHeader: 1234\r\n".getBytes());
headers.write("IntHeaderMalformed: 1234XY\r\n".getBytes());
headers.write("DateHeader: Thu, 02 Mar 2006 14:34:55 +0000\r\n".getBytes());
headers.write("DateHeaderMalformed: Thu, 02 Mar 2006V 14:13:07 +0000\r\n\r\n".getBytes());
server.setResponseHeaders(headers.toByteArray());
h.checkPoint("getHeaderFields()");
Map fields = conn.getHeaderFields();
// check that map is unmodifiable
try
{
fields.clear();
h.check(false);
}
catch (UnsupportedOperationException e)
{
h.check(true);
}
// exactly 7 headers with status and server header
h.check(fields.size() == 7);
// check for list and that case matters for key
Object obj = fields.get("Key1");
if (! (obj instanceof List))
h.check(false);
else
{
h.check(true);
List value = (List) obj;
h.check(value.size() == 2);
h.check(value.get(0).equals("value3"));
h.check(value.get(1).equals("value, value2"));
// check that it is an unmodifiable list
try
{
value.remove(0);
h.check(false);
}
catch (UnsupportedOperationException e)
{
h.check(true);
}
}
// wrong case for key
obj = fields.get("key1");
h.check(obj == null);
// checks for getHeaderField/Key(int)
h.checkPoint("getHeaderField(int)");
// check that index 0 is the statusline
String statusline = conn.getHeaderField(0);
h.check(statusline.equals("HTTP/1.0 200 OK"));
// indexes out of bound must return null
String aboutIndex = conn.getHeaderField(44);
h.check(aboutIndex == null);
String belowIndex = conn.getHeaderField(-1);
h.check(belowIndex == null);
// check that correct key/value name is returned
String key1_Value = conn.getHeaderField(2);
h.check(key1_Value.equals("value, value2"));
h.checkPoint("getHeaderFieldKey(int)");
// check that index 0 is the statusline
String statuslineKey = conn.getHeaderFieldKey(0);
h.check(statuslineKey == null);
// indexes out of bound must return null
String aboutIndexKey = conn.getHeaderFieldKey(44);
h.check(aboutIndexKey == null);
String belowIndexKey = conn.getHeaderFieldKey(-1);
h.check(belowIndexKey == null);
// check that correct key/value name is returned
String key1_Key = conn.getHeaderFieldKey(2);
h.check(key1_Key.equals("Key1"));
// checks getHeaderFieldDate
h.checkPoint("getHeaderFieldDate()");
// correct date header field
long dateHeader = conn.getHeaderFieldDate("DateHeader", 5555);
h.check(dateHeader == 1141310095000L);
// missing date header field
dateHeader = conn.getHeaderFieldDate("DateHeaderXX", 5555);
h.check(dateHeader == 5555);
// malformed date header value
dateHeader = conn.getHeaderFieldDate("DateHeaderMalformed", 5555);
h.check(dateHeader == 5555);
// checks getHeaderFieldInt
h.checkPoint("getHeaderFieldInt()");
// correct int header field
int intHeader = conn.getHeaderFieldInt("IntHeader", 5555);
h.check(intHeader == 1234);
// missing int header field
intHeader = conn.getHeaderFieldInt("IntHeaderXX", 5555);
h.check(intHeader == 5555);
// malformed int header value
intHeader = conn.getHeaderFieldInt("IntHeaderMalformed", 5555);
h.check(intHeader == 5555);
// checks that the convenience methods of the headers
// not set in this test return the correct default values
h.checkPoint("convenience methods");
h.check(conn.getLastModified() == 0);
h.check(conn.getDate() == 0);
h.check(conn.getExpiration() == 0);
h.check(conn.getContentEncoding() == null);
h.check(conn.getContentType() == null);
h.check(conn.getContentLength() == -1);
// checks getHeaderField(String)
h.checkPoint("getHeaderField(String)");
String field = conn.getHeaderField("Server");
String field1 = conn.getHeaderField("server");
h.check(field.equals("TestServer"));
h.check(field == field1);
String none = conn.getHeaderField("NotExistent");
h.check(none == null);
// check for multiple times same key
String field_key1 = conn.getHeaderField("Key1");
h.check(field_key1.equals("value3"));
}
catch (IOException e)
{
h.debug("Unexpected IOException");
h.debug(e);
}
catch (RuntimeException e)
{
h.debug("Unexpected IOException");
h.debug(e);
}
}
public void test_LowerUpperCaseHeaders(TestHarness h, TestHttpServer server)
{
try
{
URL url = new URL("http://localhost:8080/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
// construct what should be returned by the test server as headers
ByteArrayOutputStream headers = new ByteArrayOutputStream();
headers.write("HTTP/1.0 200 OK\r\n".getBytes());
headers.write("Server: TestServer\r\n".getBytes());
headers.write("AnotherKey: value\r\n".getBytes());
headers.write("Key: value\r\n".getBytes());
headers.write("Key: value1\r\n".getBytes());
headers.write("key: value2\r\n".getBytes());
headers.write("key: value3\r\n\r\n".getBytes());
server.setResponseHeaders(headers.toByteArray());
h.checkPoint("LowerUpperCase header fields tests");
Map fields = conn.getHeaderFields();
// exactly 5 headers with status and server header
h.check(fields.size() == 5);
// check for list and that case matters for key
List value = (List) fields.get("Key");
h.check(value.size() == 2);
h.check(value.get(0).equals("value1"));
List value2 = (List) fields.get("key");
h.check(value2.size() == 2);
h.check(value2.get(0).equals("value3"));
List value3 = (List) fields.get("AnotherKey");
h.check(value3.get(0).equals("value"));
value3 = (List) fields.get("anotherkey");
h.check(value3 == null);
// checks getHeaderField(String)
String field = conn.getHeaderField("Key");
String field1 = conn.getHeaderField("key");
h.check(field.equals("value3"));
h.check(field == field1);
}
catch (IOException e)
{
h.debug("Unexpected IOException");
h.debug(e);
}
catch (RuntimeException e)
{
h.debug("Unexpected IOException");
h.debug(e);
}
}
}