Merged branch 'origin/master' into 'jetty-http2'.
This commit is contained in:
commit
2608af8f0d
|
@ -773,15 +773,14 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
|||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable failure)
|
||||
public void onCompleteFailure(Throwable failure)
|
||||
{
|
||||
content.failed(failure);
|
||||
super.failed(failure);
|
||||
anyToFailure(failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void completed()
|
||||
protected void onCompleteSuccess()
|
||||
{
|
||||
// Nothing to do, since we always return false from process().
|
||||
// Termination is obtained via LastContentCallback.
|
||||
|
|
|
@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit;
|
|||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -56,6 +57,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
|
|||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
|
||||
|
@ -299,6 +301,7 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Slow
|
||||
@Test
|
||||
public void testConnectTimeoutFailsRequest() throws Exception
|
||||
|
@ -330,6 +333,7 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
|
|||
Assert.assertNotNull(request.getAbortCause());
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Slow
|
||||
@Test
|
||||
public void testConnectTimeoutIsCancelledByShorterTimeout() throws Exception
|
||||
|
|
|
@ -82,7 +82,7 @@ public class Flusher
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void completed()
|
||||
protected void onCompleteSuccess()
|
||||
{
|
||||
// We never return Action.SUCCEEDED, so this method is never called.
|
||||
throw new IllegalStateException();
|
||||
|
@ -98,7 +98,7 @@ public class Flusher
|
|||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
public void onCompleteFailure(Throwable x)
|
||||
{
|
||||
if (active != null)
|
||||
active.failed(x);
|
||||
|
@ -111,8 +111,6 @@ public class Flusher
|
|||
break;
|
||||
result.failed(x);
|
||||
}
|
||||
|
||||
super.failed(x);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -713,7 +713,13 @@ public abstract class HTTP2Session implements ISession, Parser.Listener
|
|||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
protected void onCompleteSuccess()
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCompleteFailure(Throwable x)
|
||||
{
|
||||
LOG.debug(x);
|
||||
lease.recycle();
|
||||
|
@ -723,13 +729,6 @@ public abstract class HTTP2Session implements ISession, Parser.Listener
|
|||
entry.failed(x);
|
||||
}
|
||||
active.clear();
|
||||
super.failed(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void completed()
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
public void close()
|
||||
|
|
|
@ -20,13 +20,13 @@ package org.eclipse.jetty.server;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.WritePendingException;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
import org.eclipse.jetty.http.HttpGenerator;
|
||||
import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
|
||||
import org.eclipse.jetty.http.HttpParser;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.io.AbstractConnection;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
|
@ -58,7 +58,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
private final HttpParser _parser;
|
||||
private volatile ByteBuffer _requestBuffer = null;
|
||||
private volatile ByteBuffer _chunk = null;
|
||||
|
||||
private final SendCallback _sendCallback = new SendCallback();
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Get the current connection that this thread is dispatched to.
|
||||
|
@ -418,54 +418,80 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
fillInterested();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose()
|
||||
{
|
||||
if (_sendCallback.isInUse())
|
||||
{
|
||||
LOG.warn("Closed with pending write:"+this);
|
||||
_sendCallback.failed(new EofException("Connection closed"));
|
||||
}
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
onFillable();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void send(ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
|
||||
{
|
||||
if (info==null)
|
||||
new ContentCallback(content,lastContent,callback).iterate();
|
||||
else
|
||||
{
|
||||
// If we are still expecting a 100 continues
|
||||
if (_channel.isExpecting100Continue())
|
||||
// If we are still expecting a 100 continues when we commit
|
||||
if (info!=null && _channel.isExpecting100Continue())
|
||||
// then we can't be persistent
|
||||
_generator.setPersistent(false);
|
||||
new CommitCallback(info,content,lastContent,callback).iterate();
|
||||
}
|
||||
|
||||
_sendCallback.reset(info,content,lastContent,callback);
|
||||
_sendCallback.iterate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(ByteBuffer content, boolean lastContent, Callback callback)
|
||||
{
|
||||
new ContentCallback(content,lastContent,callback).iterate();
|
||||
_sendCallback.reset(null,content,lastContent,callback);
|
||||
_sendCallback.iterate();
|
||||
}
|
||||
|
||||
|
||||
private class CommitCallback extends IteratingCallback
|
||||
private class SendCallback extends IteratingCallback
|
||||
{
|
||||
final ByteBuffer _content;
|
||||
final boolean _lastContent;
|
||||
final ResponseInfo _info;
|
||||
final Callback _callback;
|
||||
ByteBuffer _header;
|
||||
private ResponseInfo _info;
|
||||
private ByteBuffer _content;
|
||||
private boolean _lastContent;
|
||||
private Callback _callback;
|
||||
private ByteBuffer _header;
|
||||
private boolean _shutdownOut;
|
||||
|
||||
CommitCallback(ResponseInfo info, ByteBuffer content, boolean last, Callback callback)
|
||||
private SendCallback()
|
||||
{
|
||||
super(true);
|
||||
}
|
||||
|
||||
private void reset(ResponseInfo info, ByteBuffer content, boolean last, Callback callback)
|
||||
{
|
||||
if (reset())
|
||||
{
|
||||
_info = info;
|
||||
_content = content;
|
||||
_lastContent = last;
|
||||
_callback = callback;
|
||||
_header = null;
|
||||
_shutdownOut = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
callback.failed(new WritePendingException());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action process() throws Exception
|
||||
{
|
||||
if (_callback==null)
|
||||
throw new IllegalStateException();
|
||||
|
||||
ByteBuffer chunk = _chunk;
|
||||
while (true)
|
||||
{
|
||||
|
@ -548,17 +574,12 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
}
|
||||
case SHUTDOWN_OUT:
|
||||
{
|
||||
getEndPoint().shutdownOutput();
|
||||
_shutdownOut=true;
|
||||
continue;
|
||||
}
|
||||
case DONE:
|
||||
{
|
||||
if (_header!=null)
|
||||
{
|
||||
// don't release header in spare content buffer
|
||||
if (!_lastContent || _content==null || !_content.hasArray() || !_header.hasArray() || _content.array()!=_header.array())
|
||||
_bufferPool.release(_header);
|
||||
}
|
||||
releaseHeader();
|
||||
return Action.SUCCEEDED;
|
||||
}
|
||||
case CONTINUE:
|
||||
|
@ -573,113 +594,36 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void completed()
|
||||
private void releaseHeader()
|
||||
{
|
||||
ByteBuffer h=_header;
|
||||
_header=null;
|
||||
if (h!=null)
|
||||
_bufferPool.release(h);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCompleteSuccess()
|
||||
{
|
||||
releaseHeader();
|
||||
_callback.succeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Throwable x)
|
||||
{
|
||||
super.failed(x);
|
||||
failedCallback(_callback,x);
|
||||
}
|
||||
}
|
||||
|
||||
private class ContentCallback extends IteratingCallback
|
||||
{
|
||||
final ByteBuffer _content;
|
||||
final boolean _lastContent;
|
||||
final Callback _callback;
|
||||
|
||||
ContentCallback(ByteBuffer content, boolean last, Callback callback)
|
||||
{
|
||||
_content=content;
|
||||
_lastContent=last;
|
||||
_callback=callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action process() throws Exception
|
||||
{
|
||||
ByteBuffer chunk = _chunk;
|
||||
while (true)
|
||||
{
|
||||
HttpGenerator.Result result = _generator.generateResponse(null, null, chunk, _content, _lastContent);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} generate: {} ({},{})@{}",
|
||||
this,
|
||||
result,
|
||||
BufferUtil.toSummaryString(_content),
|
||||
_lastContent,
|
||||
_generator.getState());
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case NEED_HEADER:
|
||||
throw new IllegalStateException();
|
||||
case NEED_CHUNK:
|
||||
{
|
||||
chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT);
|
||||
continue;
|
||||
}
|
||||
case FLUSH:
|
||||
{
|
||||
// Don't write the chunk or the content if this is a HEAD response
|
||||
if (_channel.getRequest().isHead())
|
||||
{
|
||||
BufferUtil.clear(chunk);
|
||||
BufferUtil.clear(_content);
|
||||
continue;
|
||||
}
|
||||
else if (BufferUtil.hasContent(chunk))
|
||||
{
|
||||
if (BufferUtil.hasContent(_content))
|
||||
getEndPoint().write(this, chunk, _content);
|
||||
else
|
||||
getEndPoint().write(this, chunk);
|
||||
}
|
||||
else if (BufferUtil.hasContent(_content))
|
||||
{
|
||||
getEndPoint().write(this, _content);
|
||||
}
|
||||
else
|
||||
continue;
|
||||
return Action.SCHEDULED;
|
||||
}
|
||||
case SHUTDOWN_OUT:
|
||||
{
|
||||
if (_shutdownOut)
|
||||
getEndPoint().shutdownOutput();
|
||||
continue;
|
||||
}
|
||||
case DONE:
|
||||
{
|
||||
return Action.SUCCEEDED;
|
||||
}
|
||||
case CONTINUE:
|
||||
{
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException("generateResponse="+result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void completed()
|
||||
public void onCompleteFailure(final Throwable x)
|
||||
{
|
||||
_callback.succeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Throwable x)
|
||||
{
|
||||
super.failed(x);
|
||||
releaseHeader();
|
||||
failedCallback(_callback,x);
|
||||
if (_shutdownOut)
|
||||
getEndPoint().shutdownOutput();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("SendCB@%x{s=%s,i=%s,cb=%s}",hashCode(),getState(),_info,_callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -690,5 +634,4 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
// response is bad either with RST or by abnormal completion of chunked response.
|
||||
getEndPoint().close();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -799,7 +799,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
private abstract class AsyncICB extends IteratingCallback
|
||||
{
|
||||
@Override
|
||||
protected void completed()
|
||||
protected void onCompleteSuccess()
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
|
@ -828,9 +828,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable e)
|
||||
public void onCompleteFailure(Throwable e)
|
||||
{
|
||||
super.failed(e);
|
||||
_onError=e;
|
||||
_channel.getState().onWritePossible();
|
||||
}
|
||||
|
@ -950,9 +949,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void completed()
|
||||
protected void onCompleteSuccess()
|
||||
{
|
||||
super.completed();
|
||||
super.onCompleteSuccess();
|
||||
if (_complete)
|
||||
closed();
|
||||
}
|
||||
|
@ -1015,9 +1014,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
public void onCompleteFailure(Throwable x)
|
||||
{
|
||||
super.failed(x);
|
||||
super.onCompleteFailure(x);
|
||||
_channel.getByteBufferPool().release(_buffer);
|
||||
try
|
||||
{
|
||||
|
@ -1079,9 +1078,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
public void onCompleteFailure(Throwable x)
|
||||
{
|
||||
super.failed(x);
|
||||
super.onCompleteFailure(x);
|
||||
_channel.getByteBufferPool().release(_buffer);
|
||||
try
|
||||
{
|
||||
|
@ -1093,5 +1092,4 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -72,7 +72,9 @@ public class HttpConnectionTest
|
|||
connector.setIdleTimeout(500);
|
||||
server.addConnector(connector);
|
||||
server.setHandler(new DumpHandler());
|
||||
server.addBean(new ErrorHandler());
|
||||
ErrorHandler eh=new ErrorHandler();
|
||||
eh.setServer(server);
|
||||
server.addBean(eh);
|
||||
server.start();
|
||||
}
|
||||
|
||||
|
|
|
@ -125,6 +125,40 @@ public class LocalConnectorTest
|
|||
assertThat(response,containsString("pathInfo=/R2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManyGETs() throws Exception
|
||||
{
|
||||
String response=_connector.getResponses(
|
||||
"GET /R1 HTTP/1.1\r\n"+
|
||||
"Host: localhost\r\n"+
|
||||
"\r\n"+
|
||||
"GET /R2 HTTP/1.1\r\n"+
|
||||
"Host: localhost\r\n"+
|
||||
"\r\n"+
|
||||
"GET /R3 HTTP/1.1\r\n"+
|
||||
"Host: localhost\r\n"+
|
||||
"\r\n"+
|
||||
"GET /R4 HTTP/1.1\r\n"+
|
||||
"Host: localhost\r\n"+
|
||||
"\r\n"+
|
||||
"GET /R5 HTTP/1.1\r\n"+
|
||||
"Host: localhost\r\n"+
|
||||
"\r\n"+
|
||||
"GET /R6 HTTP/1.1\r\n"+
|
||||
"Host: localhost\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"\r\n");
|
||||
|
||||
String r=response;
|
||||
|
||||
for (int i=1;i<=6;i++)
|
||||
{
|
||||
assertThat(r,containsString("HTTP/1.1 200 OK"));
|
||||
assertThat(r,containsString("pathInfo=/R"+i));
|
||||
r=r.substring(r.indexOf("</html>")+8);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGETandGET() throws Exception
|
||||
{
|
||||
|
|
|
@ -18,18 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.server;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
|
@ -43,7 +31,6 @@ import java.util.Arrays;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.servlet.MultipartConfigElement;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
|
@ -68,6 +55,16 @@ import org.junit.After;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class RequestTest
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(RequestTest.class);
|
||||
|
@ -130,6 +127,41 @@ public class RequestTest
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyHeaders() throws Exception
|
||||
{
|
||||
_handler._checker = new RequestTester()
|
||||
{
|
||||
@Override
|
||||
public boolean check(HttpServletRequest request,HttpServletResponse response)
|
||||
{
|
||||
assertNotNull(request.getLocale());
|
||||
assertTrue(request.getLocales().hasMoreElements());
|
||||
assertNull(request.getContentType());
|
||||
assertNull(request.getCharacterEncoding());
|
||||
assertEquals(0,request.getQueryString().length());
|
||||
assertEquals(-1,request.getContentLength());
|
||||
assertNull(request.getCookies());
|
||||
assertNull(request.getHeader("Name"));
|
||||
assertFalse(request.getHeaders("Name").hasMoreElements());
|
||||
assertEquals(-1,request.getDateHeader("Name"));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
String request="GET /? HTTP/1.1\r\n"+
|
||||
"Host: whatever\r\n"+
|
||||
"Connection: close\n"+
|
||||
"Content-Type: \n"+
|
||||
"Accept-Language: \n"+
|
||||
"Cookie: \n"+
|
||||
"Name: \n"+
|
||||
"\n";
|
||||
|
||||
String responses=_connector.getResponses(request);
|
||||
assertTrue(responses.startsWith("HTTP/1.1 200"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiPartNoConfig() throws Exception
|
||||
{
|
||||
|
@ -292,7 +324,7 @@ public class RequestTest
|
|||
@Test
|
||||
public void testContentTypeEncoding() throws Exception
|
||||
{
|
||||
final ArrayList<String> results = new ArrayList<String>();
|
||||
final ArrayList<String> results = new ArrayList<>();
|
||||
_handler._checker = new RequestTester()
|
||||
{
|
||||
@Override
|
||||
|
@ -344,7 +376,7 @@ public class RequestTest
|
|||
@Test
|
||||
public void testHostPort() throws Exception
|
||||
{
|
||||
final ArrayList<String> results = new ArrayList<String>();
|
||||
final ArrayList<String> results = new ArrayList<>();
|
||||
_handler._checker = new RequestTester()
|
||||
{
|
||||
@Override
|
||||
|
@ -781,9 +813,9 @@ public class RequestTest
|
|||
"\n",
|
||||
200, TimeUnit.MILLISECONDS
|
||||
);
|
||||
assertThat(response,containsString("200"));
|
||||
assertThat(response,Matchers.not(containsString("Connection: close")));
|
||||
assertThat(response,containsString("Hello World"));
|
||||
assertThat(response, Matchers.containsString("200"));
|
||||
assertThat(response, Matchers.not(Matchers.containsString("Connection: close")));
|
||||
assertThat(response, Matchers.containsString("Hello World"));
|
||||
|
||||
response=_connector.getResponses(
|
||||
"GET / HTTP/1.1\n"+
|
||||
|
@ -791,9 +823,9 @@ public class RequestTest
|
|||
"Connection: close\n"+
|
||||
"\n"
|
||||
);
|
||||
assertThat(response,containsString("200"));
|
||||
assertThat(response,containsString("Connection: close"));
|
||||
assertThat(response,containsString("Hello World"));
|
||||
assertThat(response, Matchers.containsString("200"));
|
||||
assertThat(response, Matchers.containsString("Connection: close"));
|
||||
assertThat(response, Matchers.containsString("Hello World"));
|
||||
|
||||
response=_connector.getResponses(
|
||||
"GET / HTTP/1.1\n"+
|
||||
|
@ -802,18 +834,18 @@ public class RequestTest
|
|||
"\n"
|
||||
);
|
||||
|
||||
assertThat(response,containsString("200"));
|
||||
assertThat(response,containsString("Connection: close"));
|
||||
assertThat(response,containsString("Hello World"));
|
||||
assertThat(response, Matchers.containsString("200"));
|
||||
assertThat(response, Matchers.containsString("Connection: close"));
|
||||
assertThat(response, Matchers.containsString("Hello World"));
|
||||
|
||||
response=_connector.getResponses(
|
||||
"GET / HTTP/1.0\n"+
|
||||
"Host: whatever\n"+
|
||||
"\n"
|
||||
);
|
||||
assertThat(response,containsString("200"));
|
||||
assertThat(response,not(containsString("Connection: close")));
|
||||
assertThat(response,containsString("Hello World"));
|
||||
assertThat(response, Matchers.containsString("200"));
|
||||
assertThat(response, Matchers.not(Matchers.containsString("Connection: close")));
|
||||
assertThat(response, Matchers.containsString("Hello World"));
|
||||
|
||||
response=_connector.getResponses(
|
||||
"GET / HTTP/1.0\n"+
|
||||
|
@ -821,8 +853,8 @@ public class RequestTest
|
|||
"Connection: Other, close\n"+
|
||||
"\n"
|
||||
);
|
||||
assertThat(response,containsString("200"));
|
||||
assertThat(response,containsString("Hello World"));
|
||||
assertThat(response, Matchers.containsString("200"));
|
||||
assertThat(response, Matchers.containsString("Hello World"));
|
||||
|
||||
response=_connector.getResponses(
|
||||
"GET / HTTP/1.0\n"+
|
||||
|
@ -831,9 +863,9 @@ public class RequestTest
|
|||
"\n",
|
||||
200, TimeUnit.MILLISECONDS
|
||||
);
|
||||
assertThat(response,containsString("200"));
|
||||
assertThat(response, Matchers.containsString("200"));
|
||||
assertThat(response, Matchers.containsString("Connection: keep-alive"));
|
||||
assertThat(response,containsString("Hello World"));
|
||||
assertThat(response, Matchers.containsString("Hello World"));
|
||||
|
||||
_handler._checker = new RequestTester()
|
||||
{
|
||||
|
@ -853,9 +885,9 @@ public class RequestTest
|
|||
"\n",
|
||||
200, TimeUnit.MILLISECONDS
|
||||
);
|
||||
assertThat(response,containsString("200"));
|
||||
assertThat(response,containsString("Connection: TE,Other"));
|
||||
assertThat(response,containsString("Hello World"));
|
||||
assertThat(response, Matchers.containsString("200"));
|
||||
assertThat(response, Matchers.containsString("Connection: TE,Other"));
|
||||
assertThat(response, Matchers.containsString("Hello World"));
|
||||
|
||||
response=_connector.getResponses(
|
||||
"GET / HTTP/1.1\n"+
|
||||
|
@ -871,7 +903,7 @@ public class RequestTest
|
|||
@Test
|
||||
public void testCookies() throws Exception
|
||||
{
|
||||
final ArrayList<Cookie> cookies = new ArrayList<Cookie>();
|
||||
final ArrayList<Cookie> cookies = new ArrayList<>();
|
||||
|
||||
_handler._checker = new RequestTester()
|
||||
{
|
||||
|
@ -927,7 +959,6 @@ public class RequestTest
|
|||
assertEquals("other", cookies.get(1).getName());
|
||||
assertEquals("quoted=;value", cookies.get(1).getValue());
|
||||
|
||||
|
||||
cookies.clear();
|
||||
response=_connector.getResponses(
|
||||
"GET /other HTTP/1.1\n"+
|
||||
|
@ -942,7 +973,8 @@ public class RequestTest
|
|||
"Connection: close\n"+
|
||||
"\n"
|
||||
);
|
||||
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
|
||||
assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
|
||||
assertThat(response.substring(15), Matchers.containsString("HTTP/1.1 200 OK"));
|
||||
assertEquals(4,cookies.size());
|
||||
assertEquals("name", cookies.get(0).getName());
|
||||
assertEquals("value", cookies.get(0).getValue());
|
||||
|
@ -966,7 +998,8 @@ public class RequestTest
|
|||
"Connection: close\n"+
|
||||
"\n"
|
||||
);
|
||||
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
|
||||
assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
|
||||
assertThat(response.substring(15), Matchers.containsString("HTTP/1.1 200 OK"));
|
||||
assertEquals(4,cookies.size());
|
||||
assertEquals("name", cookies.get(0).getName());
|
||||
assertEquals("value", cookies.get(0).getValue());
|
||||
|
@ -1025,6 +1058,78 @@ public class RequestTest
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCookieLeak() throws Exception
|
||||
{
|
||||
final String[] cookie=new String[10];
|
||||
|
||||
_handler._checker = new RequestTester()
|
||||
{
|
||||
@Override
|
||||
public boolean check(HttpServletRequest request,HttpServletResponse response)
|
||||
{
|
||||
for (int i=0;i<cookie.length; i++)
|
||||
cookie[i]=null;
|
||||
|
||||
Cookie[] cookies = request.getCookies();
|
||||
for (int i=0;cookies!=null && i<cookies.length; i++)
|
||||
{
|
||||
cookie[i]=cookies[i].getValue();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
String request="POST / HTTP/1.1\r\n"+
|
||||
"Host: whatever\r\n"+
|
||||
"Cookie: other=cookie\r\n"+
|
||||
"\r\n"
|
||||
+
|
||||
"POST / HTTP/1.1\r\n"+
|
||||
"Host: whatever\r\n"+
|
||||
"Cookie: name=value\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"\r\n";
|
||||
|
||||
_connector.getResponses(request);
|
||||
|
||||
assertEquals("value",cookie[0]);
|
||||
assertEquals(null,cookie[1]);
|
||||
|
||||
request="POST / HTTP/1.1\r\n"+
|
||||
"Host: whatever\r\n"+
|
||||
"Cookie: name=value\r\n"+
|
||||
"\r\n"
|
||||
+
|
||||
"POST / HTTP/1.1\r\n"+
|
||||
"Host: whatever\r\n"+
|
||||
"Cookie:\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"\r\n";
|
||||
|
||||
_connector.getResponses(request);
|
||||
assertEquals(null,cookie[0]);
|
||||
assertEquals(null,cookie[1]);
|
||||
|
||||
request="POST / HTTP/1.1\r\n"+
|
||||
"Host: whatever\r\n"+
|
||||
"Cookie: name=value\r\n"+
|
||||
"Cookie: other=cookie\r\n"+
|
||||
"\r\n"
|
||||
+
|
||||
"POST / HTTP/1.1\r\n"+
|
||||
"Host: whatever\r\n"+
|
||||
"Cookie: name=value\r\n"+
|
||||
"Cookie:\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"\r\n";
|
||||
|
||||
_connector.getResponses(request);
|
||||
|
||||
assertEquals("value",cookie[0]);
|
||||
assertEquals(null,cookie[1]);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHashDOS() throws Exception
|
||||
|
|
|
@ -218,7 +218,7 @@ public class Flusher
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void completed()
|
||||
protected void onCompleteSuccess()
|
||||
{
|
||||
// will never be called as process always returns SCHEDULED or IDLE
|
||||
throw new IllegalStateException();
|
||||
|
@ -242,7 +242,7 @@ public class Flusher
|
|||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
public void onCompleteFailure(Throwable x)
|
||||
{
|
||||
List<StandardSession.FrameBytes> failed = new ArrayList<>();
|
||||
synchronized (lock)
|
||||
|
@ -261,7 +261,6 @@ public class Flusher
|
|||
// Notify outside the synchronized block.
|
||||
for (StandardSession.FrameBytes frame : failed)
|
||||
frame.failed(x);
|
||||
super.failed(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,14 +71,25 @@ public abstract class IteratingCallback implements Callback
|
|||
/**
|
||||
* Indicates that {@link #process()} has completed the overall job.
|
||||
*/
|
||||
SUCCEEDED,
|
||||
/**
|
||||
* Indicates that {@link #process()} has failed the overall job.
|
||||
*/
|
||||
FAILED
|
||||
SUCCEEDED
|
||||
}
|
||||
|
||||
private final AtomicReference<State> _state = new AtomicReference<>(State.INACTIVE);
|
||||
private final AtomicReference<State> _state;
|
||||
|
||||
protected IteratingCallback()
|
||||
{
|
||||
_state = new AtomicReference<>(State.INACTIVE);
|
||||
}
|
||||
|
||||
protected IteratingCallback(boolean needReset)
|
||||
{
|
||||
_state = new AtomicReference<>(needReset?State.SUCCEEDED:State.INACTIVE);
|
||||
}
|
||||
|
||||
protected State getState()
|
||||
{
|
||||
return _state.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called by {@link #iterate()} to process the sub task.
|
||||
|
@ -91,7 +102,6 @@ public abstract class IteratingCallback implements Callback
|
|||
* <li>{@link Action#SCHEDULED} when the sub task asynchronous execution
|
||||
* has been started</li>
|
||||
* <li>{@link Action#SUCCEEDED} when the overall job is completed</li>
|
||||
* <li>{@link Action#FAILED} when the overall job cannot be completed</li>
|
||||
* </ul>
|
||||
*
|
||||
* @throws Exception if the sub task processing throws
|
||||
|
@ -101,7 +111,12 @@ public abstract class IteratingCallback implements Callback
|
|||
/**
|
||||
* Invoked when the overall task has completed successfully.
|
||||
*/
|
||||
protected abstract void completed();
|
||||
protected abstract void onCompleteSuccess();
|
||||
|
||||
/**
|
||||
* Invoked when the overall task has completely failed.
|
||||
*/
|
||||
protected abstract void onCompleteFailure(Throwable x);
|
||||
|
||||
/**
|
||||
* This method must be invoked by applications to start the processing
|
||||
|
@ -196,13 +211,7 @@ public abstract class IteratingCallback implements Callback
|
|||
case SUCCEEDED:
|
||||
{
|
||||
// The overall job has completed.
|
||||
if (completeSuccess())
|
||||
completed();
|
||||
return true;
|
||||
}
|
||||
case FAILED:
|
||||
{
|
||||
completeFailure();
|
||||
completeSuccess();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
|
@ -265,43 +274,49 @@ public abstract class IteratingCallback implements Callback
|
|||
* {@code super.failed(Throwable)}.
|
||||
*/
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
public final void failed(Throwable x)
|
||||
{
|
||||
completeFailure();
|
||||
completeFailure(x);
|
||||
}
|
||||
|
||||
private boolean completeSuccess()
|
||||
protected void completeSuccess()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
State current = _state.get();
|
||||
if (current == State.FAILED)
|
||||
{
|
||||
// Success arrived too late, sorry.
|
||||
return false;
|
||||
}
|
||||
else
|
||||
switch(current)
|
||||
{
|
||||
case SUCCEEDED:
|
||||
case FAILED:
|
||||
// Already complete!.
|
||||
return;
|
||||
default:
|
||||
if (_state.compareAndSet(current, State.SUCCEEDED))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void completeFailure()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
State current = _state.get();
|
||||
if (current == State.SUCCEEDED)
|
||||
{
|
||||
// Failed arrived too late, sorry.
|
||||
onCompleteSuccess();
|
||||
return;
|
||||
}
|
||||
else
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void completeFailure(Throwable x)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
State current = _state.get();
|
||||
switch(current)
|
||||
{
|
||||
case SUCCEEDED:
|
||||
case FAILED:
|
||||
// Already complete!.
|
||||
return;
|
||||
default:
|
||||
if (_state.compareAndSet(current, State.FAILED))
|
||||
break;
|
||||
{
|
||||
onCompleteFailure(x);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -314,6 +329,23 @@ public abstract class IteratingCallback implements Callback
|
|||
return _state.get() == State.INACTIVE;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return true if the callback is not INACTIVE, FAILED or SUCCEEDED.
|
||||
*/
|
||||
public boolean isInUse()
|
||||
{
|
||||
switch(_state.get())
|
||||
{
|
||||
case INACTIVE:
|
||||
case FAILED:
|
||||
case SUCCEEDED:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this callback has failed
|
||||
*/
|
||||
|
@ -330,6 +362,36 @@ public abstract class IteratingCallback implements Callback
|
|||
return _state.get() == State.SUCCEEDED;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Reset the callback
|
||||
* <p>A callback can only be reset to INACTIVE from the SUCCEEDED or FAILED states or if it is already INACTIVE.
|
||||
* @return True if the reset was successful
|
||||
*/
|
||||
public boolean reset()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
switch(_state.get())
|
||||
{
|
||||
case INACTIVE:
|
||||
return true;
|
||||
|
||||
case SUCCEEDED:
|
||||
if (_state.compareAndSet(State.SUCCEEDED, State.INACTIVE))
|
||||
return true;
|
||||
break;
|
||||
|
||||
case FAILED:
|
||||
if (_state.compareAndSet(State.FAILED, State.INACTIVE))
|
||||
return true;
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
|
|
@ -48,15 +48,14 @@ public abstract class IteratingNestedCallback extends IteratingCallback
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void completed()
|
||||
protected void onCompleteSuccess()
|
||||
{
|
||||
_callback.succeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
protected void onCompleteFailure(Throwable x)
|
||||
{
|
||||
super.failed(x);
|
||||
_callback.failed(x);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.net.URISyntaxException;
|
|||
import java.net.URL;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.file.DirectoryIteratorException;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
|
@ -265,13 +266,32 @@ public class PathResource extends Resource
|
|||
}
|
||||
|
||||
@Override
|
||||
public String[] list()
|
||||
public URI getAlias()
|
||||
{
|
||||
if (Files.isSymbolicLink(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
List<String> entries = new ArrayList<>();
|
||||
return path.toRealPath().toUri();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOG.debug(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] list()
|
||||
{
|
||||
try (DirectoryStream<Path> dir = Files.newDirectoryStream(path))
|
||||
{
|
||||
List<String> entries = new ArrayList<>();
|
||||
for (Path entry : dir)
|
||||
{
|
||||
String name = entry.getFileName().toString();
|
||||
|
@ -283,13 +303,16 @@ public class PathResource extends Resource
|
|||
|
||||
entries.add(name);
|
||||
}
|
||||
}
|
||||
int size = entries.size();
|
||||
return entries.toArray(new String[size]);
|
||||
}
|
||||
catch (DirectoryIteratorException e)
|
||||
{
|
||||
LOG.debug(e);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOG.ignore(e);
|
||||
LOG.debug(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -262,16 +262,14 @@ public class IteratingCallbackTest
|
|||
int processed=0;
|
||||
|
||||
@Override
|
||||
protected void completed()
|
||||
protected void onCompleteSuccess()
|
||||
{
|
||||
completed.countDown();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
public void onCompleteFailure(Throwable x)
|
||||
{
|
||||
super.failed(x);
|
||||
completed.countDown();
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,10 @@ import java.net.URI;
|
|||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.file.FileSystemException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
@ -84,7 +87,7 @@ public abstract class AbstractFSResourceTest
|
|||
}
|
||||
else if (OS.IS_WINDOWS)
|
||||
{
|
||||
newResource(new URI("file:/dev/null"));
|
||||
newResource(new URI("file://some\"text\""));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,11 +327,56 @@ public abstract class AbstractFSResourceTest
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSymlink() throws Exception
|
||||
{
|
||||
File dir = testdir.getDir();
|
||||
|
||||
Path foo = new File(dir, "foo").toPath();
|
||||
Path bar = new File(dir, "bar").toPath();
|
||||
|
||||
try
|
||||
{
|
||||
Files.createFile(foo);
|
||||
Files.createSymbolicLink(bar,foo);
|
||||
}
|
||||
catch (UnsupportedOperationException | FileSystemException e)
|
||||
{
|
||||
// if unable to create symlink, no point testing the rest
|
||||
// this is the path that Microsoft Windows takes.
|
||||
assumeNoException(e);
|
||||
}
|
||||
|
||||
try (Resource base = newResource(testdir.getDir()))
|
||||
{
|
||||
Resource resFoo = base.addPath("foo");
|
||||
Resource resBar = base.addPath("bar");
|
||||
|
||||
// Access to the same resource, but via a symlink means that they are not equivalent
|
||||
assertThat("foo.equals(bar)", resFoo.equals(resBar), is(false));
|
||||
|
||||
assertThat("foo.alias", resFoo.getAlias(), nullValue());
|
||||
assertThat("bar.alias", resBar.getAlias(), is(foo.toUri()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSemicolon() throws Exception
|
||||
{
|
||||
assumeTrue(!OS.IS_WINDOWS);
|
||||
createEmptyFile("foo;");
|
||||
File dir = testdir.getDir();
|
||||
|
||||
try
|
||||
{
|
||||
// attempt to create file
|
||||
Path foo = new File(dir, "foo;").toPath();
|
||||
Files.createFile(foo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// if unable to create file, no point testing the rest
|
||||
// this is the path that Microsoft Windows takes.
|
||||
assumeNoException(e);
|
||||
}
|
||||
|
||||
try (Resource base = newResource(testdir.getDir()))
|
||||
{
|
||||
|
|
|
@ -386,11 +386,19 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void completed()
|
||||
protected void onCompleteSuccess()
|
||||
{
|
||||
// This IteratingCallback never completes.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCompleteFailure(Throwable x)
|
||||
{
|
||||
// This IteratingCallback never fails.
|
||||
// The callback are those provided by WriteCallback (implemented
|
||||
// below) and even in case of writeFailed() we call succeeded().
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSuccess()
|
||||
{
|
||||
|
|
|
@ -368,11 +368,20 @@ public abstract class CompressExtension extends AbstractExtension
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void completed()
|
||||
protected void onCompleteSuccess()
|
||||
{
|
||||
// This IteratingCallback never completes.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCompleteFailure(Throwable x)
|
||||
{
|
||||
// Fail all the frames in the queue.
|
||||
FrameEntry entry;
|
||||
while ((entry = entries.poll()) != null)
|
||||
notifyCallbackFailure(entry.callback, x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSuccess()
|
||||
{
|
||||
|
@ -388,10 +397,6 @@ public abstract class CompressExtension extends AbstractExtension
|
|||
// If something went wrong, very likely the compression context
|
||||
// will be invalid, so we need to fail this IteratingCallback.
|
||||
failed(x);
|
||||
// Now no more frames can be queued, fail those in the queue.
|
||||
FrameEntry entry;
|
||||
while ((entry = entries.poll()) != null)
|
||||
notifyCallbackFailure(entry.callback, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -150,11 +150,19 @@ public class FragmentExtension extends AbstractExtension
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void completed()
|
||||
protected void onCompleteSuccess()
|
||||
{
|
||||
// This IteratingCallback never completes.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCompleteFailure(Throwable x)
|
||||
{
|
||||
// This IteratingCallback never fails.
|
||||
// The callback are those provided by WriteCallback (implemented
|
||||
// below) and even in case of writeFailed() we call succeeded().
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSuccess()
|
||||
{
|
||||
|
|
|
@ -91,13 +91,13 @@ public class FrameFlusher
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void completed()
|
||||
protected void onCompleteSuccess()
|
||||
{
|
||||
// This IteratingCallback never completes.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
public void onCompleteFailure(Throwable x)
|
||||
{
|
||||
for (FrameEntry entry : entries)
|
||||
{
|
||||
|
@ -105,7 +105,6 @@ public class FrameFlusher
|
|||
entry.release();
|
||||
}
|
||||
entries.clear();
|
||||
super.failed(x);
|
||||
failure = x;
|
||||
onFailure(x);
|
||||
}
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -258,9 +258,11 @@
|
|||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.17</version>
|
||||
<configuration>
|
||||
<argLine>-showversion -Xmx1g -Xms1g -XX:+PrintGCDetails</argLine>
|
||||
<failIfNoTests>false</failIfNoTests>
|
||||
<runMode>random</runMode>
|
||||
<systemProperties>
|
||||
<!--
|
||||
<property>
|
||||
|
|
Loading…
Reference in New Issue