Index: net/socket/ssl_client_socket_unittest.cc |
diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc |
index 2639870e81f36f4fefef5ad4626ced9bc629da13..8ed4d7043d4c6cb4f5d3b86d5edd73338d46ba14 100644 |
--- a/net/socket/ssl_client_socket_unittest.cc |
+++ b/net/socket/ssl_client_socket_unittest.cc |
@@ -7,6 +7,7 @@ |
#include "base/callback_helpers.h" |
#include "base/memory/ref_counted.h" |
#include "base/run_loop.h" |
+#include "base/time/time.h" |
#include "net/base/address_list.h" |
#include "net/base/io_buffer.h" |
#include "net/base/net_errors.h" |
@@ -529,6 +530,38 @@ void FakeBlockingStreamSocket::OnReadCompleted(int result) { |
} |
} |
+// CountingStreamSocket wraps an existing StreamSocket and maintains a count of |
+// reads and writes on the socket. |
+class CountingStreamSocket : public WrappedStreamSocket { |
+ public: |
+ explicit CountingStreamSocket(scoped_ptr<StreamSocket> transport) |
+ : WrappedStreamSocket(transport.Pass()), |
+ read_count_(0), |
+ write_count_(0) {} |
+ virtual ~CountingStreamSocket() {} |
+ |
+ // Socket implementation: |
+ virtual int Read(IOBuffer* buf, |
+ int buf_len, |
+ const CompletionCallback& callback) OVERRIDE { |
+ read_count_++; |
+ return transport_->Read(buf, buf_len, callback); |
+ } |
+ virtual int Write(IOBuffer* buf, |
+ int buf_len, |
+ const CompletionCallback& callback) OVERRIDE { |
+ write_count_++; |
+ return transport_->Write(buf, buf_len, callback); |
+ } |
+ |
+ int read_count() const { return read_count_; } |
+ int write_count() const { return write_count_; } |
+ |
+ private: |
+ int read_count_; |
+ int write_count_; |
+}; |
+ |
// CompletionCallback that will delete the associated StreamSocket when |
// the callback is invoked. |
class DeleteSocketCallback : public TestCompletionCallbackBase { |
@@ -1281,6 +1314,73 @@ TEST_F(SSLClientSocketTest, Write_WithSynchronousError) { |
#endif |
} |
+// If there is a Write failure at the transport with no follow-up Read, although |
+// the write error will not be returned to the client until a future Read or |
+// Write operation, SSLClientSocket should not spin attempting to re-write on |
+// the socket. This is a regression test for part of https://crbug.com/381160. |
+TEST_F(SSLClientSocketTest, Write_WithSynchronousErrorNoRead) { |
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS, |
+ SpawnedTestServer::kLocalhost, |
+ base::FilePath()); |
+ ASSERT_TRUE(test_server.Start()); |
+ |
+ AddressList addr; |
+ ASSERT_TRUE(test_server.GetAddressList(&addr)); |
+ |
+ TestCompletionCallback callback; |
+ scoped_ptr<StreamSocket> real_transport( |
+ new TCPClientSocket(addr, NULL, NetLog::Source())); |
+ // Note: intermediate sockets' ownership are handed to |sock|, but a pointer |
+ // is retained in order to query them. |
+ scoped_ptr<SynchronousErrorStreamSocket> error_socket( |
+ new SynchronousErrorStreamSocket(real_transport.Pass())); |
+ SynchronousErrorStreamSocket* raw_error_socket = error_socket.get(); |
+ scoped_ptr<CountingStreamSocket> counting_socket( |
+ new CountingStreamSocket(error_socket.PassAs<StreamSocket>())); |
+ CountingStreamSocket* raw_counting_socket = counting_socket.get(); |
+ int rv = callback.GetResult(counting_socket->Connect(callback.callback())); |
+ EXPECT_EQ(OK, rv); |
+ |
+ // Disable TLS False Start to avoid handshake non-determinism. |
+ SSLConfig ssl_config; |
+ ssl_config.false_start_enabled = false; |
+ |
+ scoped_ptr<SSLClientSocket> sock( |
+ CreateSSLClientSocket(counting_socket.PassAs<StreamSocket>(), |
+ test_server.host_port_pair(), |
+ ssl_config)); |
+ |
+ rv = callback.GetResult(sock->Connect(callback.callback())); |
+ EXPECT_EQ(OK, rv); |
+ EXPECT_TRUE(sock->IsConnected()); |
+ |
+ // Simulate an unclean/forcible shutdown on the underlying socket. |
+ raw_error_socket->SetNextWriteError(ERR_CONNECTION_RESET); |
+ |
+ const char request_text[] = "GET / HTTP/1.0\r\n\r\n"; |
+ static const int kRequestTextSize = |
+ static_cast<int>(arraysize(request_text) - 1); |
+ scoped_refptr<IOBuffer> request_buffer(new IOBuffer(kRequestTextSize)); |
+ memcpy(request_buffer->data(), request_text, kRequestTextSize); |
+ |
+ // This write should complete synchronously, because the TLS ciphertext |
+ // can be created and placed into the outgoing buffers independent of the |
+ // underlying transport. |
+ rv = callback.GetResult( |
+ sock->Write(request_buffer.get(), kRequestTextSize, callback.callback())); |
+ EXPECT_EQ(kRequestTextSize, rv); |
+ |
+ // Let the event loop spin for a little bit of time. Even on platforms where |
+ // pumping the state machine involve thread hops, there should be no further |
+ // writes on the transport socket. |
+ int old_write_count = raw_counting_socket->write_count(); |
+ base::RunLoop loop; |
+ base::MessageLoop::current()->PostDelayedTask( |
+ FROM_HERE, loop.QuitClosure(), base::TimeDelta::FromMilliseconds(100)); |
wtc
2014/06/16 19:57:21
IMPORTANT: could this be a source of test flakines
davidben
2014/06/16 23:02:13
Yeah, it might flakily pass when it shouldn't, but
|
+ loop.Run(); |
+ EXPECT_EQ(old_write_count, raw_counting_socket->write_count()); |
+} |
+ |
// Test the full duplex mode, with Read and Write pending at the same time. |
// This test also serves as a regression test for http://crbug.com/29815. |
TEST_F(SSLClientSocketTest, Read_FullDuplex) { |