// Copyright (c) 2013 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. #include "net/spdy/spdy_write_queue.h" #include <cstddef> #include <cstring> #include <string> #include "base/basictypes.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/strings/string_number_conversions.h" #include "net/base/net_log.h" #include "net/base/request_priority.h" #include "net/spdy/spdy_buffer_producer.h" #include "net/spdy/spdy_stream.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" namespace net { namespace { class SpdyWriteQueueTest : public ::testing::Test {}; // Makes a SpdyFrameProducer producing a frame with the data in the // given string. scoped_ptr<SpdyBufferProducer> StringToProducer(const std::string& s) { scoped_ptr<char[]> data(new char[s.size()]); std::memcpy(data.get(), s.data(), s.size()); return scoped_ptr<SpdyBufferProducer>( new SimpleBufferProducer( scoped_ptr<SpdyBuffer>( new SpdyBuffer( scoped_ptr<SpdyFrame>( new SpdyFrame(data.release(), s.size(), true)))))); } // Makes a SpdyBufferProducer producing a frame with the data in the // given int (converted to a string). scoped_ptr<SpdyBufferProducer> IntToProducer(int i) { return StringToProducer(base::IntToString(i)); } // Produces a frame with the given producer and returns a copy of its // data as a string. std::string ProducerToString(scoped_ptr<SpdyBufferProducer> producer) { scoped_ptr<SpdyBuffer> buffer = producer->ProduceBuffer(); return std::string(buffer->GetRemainingData(), buffer->GetRemainingSize()); } // Produces a frame with the given producer and returns a copy of its // data as an int (converted from a string). int ProducerToInt(scoped_ptr<SpdyBufferProducer> producer) { int i = 0; EXPECT_TRUE(base::StringToInt(ProducerToString(producer.Pass()), &i)); return i; } // Makes a SpdyStream with the given priority and a NULL SpdySession // -- be careful to not call any functions that expect the session to // be there. SpdyStream* MakeTestStream(RequestPriority priority) { return new SpdyStream( SPDY_BIDIRECTIONAL_STREAM, base::WeakPtr<SpdySession>(), GURL(), priority, 0, 0, BoundNetLog()); } // Add some frame producers of different priority. The producers // should be dequeued in priority order with their associated stream. TEST_F(SpdyWriteQueueTest, DequeuesByPriority) { SpdyWriteQueue write_queue; scoped_ptr<SpdyBufferProducer> producer_low = StringToProducer("LOW"); scoped_ptr<SpdyBufferProducer> producer_medium = StringToProducer("MEDIUM"); scoped_ptr<SpdyBufferProducer> producer_highest = StringToProducer("HIGHEST"); scoped_ptr<SpdyStream> stream_medium(MakeTestStream(MEDIUM)); scoped_ptr<SpdyStream> stream_highest(MakeTestStream(HIGHEST)); // A NULL stream should still work. write_queue.Enqueue( LOW, SYN_STREAM, producer_low.Pass(), base::WeakPtr<SpdyStream>()); write_queue.Enqueue( MEDIUM, SYN_REPLY, producer_medium.Pass(), stream_medium->GetWeakPtr()); write_queue.Enqueue( HIGHEST, RST_STREAM, producer_highest.Pass(), stream_highest->GetWeakPtr()); SpdyFrameType frame_type = DATA; scoped_ptr<SpdyBufferProducer> frame_producer; base::WeakPtr<SpdyStream> stream; ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); EXPECT_EQ(RST_STREAM, frame_type); EXPECT_EQ("HIGHEST", ProducerToString(frame_producer.Pass())); EXPECT_EQ(stream_highest, stream.get()); ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); EXPECT_EQ(SYN_REPLY, frame_type); EXPECT_EQ("MEDIUM", ProducerToString(frame_producer.Pass())); EXPECT_EQ(stream_medium, stream.get()); ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); EXPECT_EQ(SYN_STREAM, frame_type); EXPECT_EQ("LOW", ProducerToString(frame_producer.Pass())); EXPECT_EQ(NULL, stream.get()); EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); } // Add some frame producers with the same priority. The producers // should be dequeued in FIFO order with their associated stream. TEST_F(SpdyWriteQueueTest, DequeuesFIFO) { SpdyWriteQueue write_queue; scoped_ptr<SpdyBufferProducer> producer1 = IntToProducer(1); scoped_ptr<SpdyBufferProducer> producer2 = IntToProducer(2); scoped_ptr<SpdyBufferProducer> producer3 = IntToProducer(3); scoped_ptr<SpdyStream> stream1(MakeTestStream(DEFAULT_PRIORITY)); scoped_ptr<SpdyStream> stream2(MakeTestStream(DEFAULT_PRIORITY)); scoped_ptr<SpdyStream> stream3(MakeTestStream(DEFAULT_PRIORITY)); write_queue.Enqueue(DEFAULT_PRIORITY, SYN_STREAM, producer1.Pass(), stream1->GetWeakPtr()); write_queue.Enqueue(DEFAULT_PRIORITY, SYN_REPLY, producer2.Pass(), stream2->GetWeakPtr()); write_queue.Enqueue(DEFAULT_PRIORITY, RST_STREAM, producer3.Pass(), stream3->GetWeakPtr()); SpdyFrameType frame_type = DATA; scoped_ptr<SpdyBufferProducer> frame_producer; base::WeakPtr<SpdyStream> stream; ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); EXPECT_EQ(SYN_STREAM, frame_type); EXPECT_EQ(1, ProducerToInt(frame_producer.Pass())); EXPECT_EQ(stream1, stream.get()); ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); EXPECT_EQ(SYN_REPLY, frame_type); EXPECT_EQ(2, ProducerToInt(frame_producer.Pass())); EXPECT_EQ(stream2, stream.get()); ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); EXPECT_EQ(RST_STREAM, frame_type); EXPECT_EQ(3, ProducerToInt(frame_producer.Pass())); EXPECT_EQ(stream3, stream.get()); EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); } // Enqueue a bunch of writes and then call // RemovePendingWritesForStream() on one of the streams. No dequeued // write should be for that stream. TEST_F(SpdyWriteQueueTest, RemovePendingWritesForStream) { SpdyWriteQueue write_queue; scoped_ptr<SpdyStream> stream1(MakeTestStream(DEFAULT_PRIORITY)); scoped_ptr<SpdyStream> stream2(MakeTestStream(DEFAULT_PRIORITY)); for (int i = 0; i < 100; ++i) { base::WeakPtr<SpdyStream> stream = (((i % 3) == 0) ? stream1 : stream2)->GetWeakPtr(); write_queue.Enqueue(DEFAULT_PRIORITY, SYN_STREAM, IntToProducer(i), stream); } write_queue.RemovePendingWritesForStream(stream2->GetWeakPtr()); for (int i = 0; i < 100; i += 3) { SpdyFrameType frame_type = DATA; scoped_ptr<SpdyBufferProducer> frame_producer; base::WeakPtr<SpdyStream> stream; ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); EXPECT_EQ(SYN_STREAM, frame_type); EXPECT_EQ(i, ProducerToInt(frame_producer.Pass())); EXPECT_EQ(stream1, stream.get()); } SpdyFrameType frame_type = DATA; scoped_ptr<SpdyBufferProducer> frame_producer; base::WeakPtr<SpdyStream> stream; EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); } // Enqueue a bunch of writes and then call // RemovePendingWritesForStreamsAfter(). No dequeued write should be for // those streams without a stream id, or with a stream_id after that // argument. TEST_F(SpdyWriteQueueTest, RemovePendingWritesForStreamsAfter) { SpdyWriteQueue write_queue; scoped_ptr<SpdyStream> stream1(MakeTestStream(DEFAULT_PRIORITY)); stream1->set_stream_id(1); scoped_ptr<SpdyStream> stream2(MakeTestStream(DEFAULT_PRIORITY)); stream2->set_stream_id(3); scoped_ptr<SpdyStream> stream3(MakeTestStream(DEFAULT_PRIORITY)); stream3->set_stream_id(5); // No stream id assigned. scoped_ptr<SpdyStream> stream4(MakeTestStream(DEFAULT_PRIORITY)); base::WeakPtr<SpdyStream> streams[] = { stream1->GetWeakPtr(), stream2->GetWeakPtr(), stream3->GetWeakPtr(), stream4->GetWeakPtr() }; for (int i = 0; i < 100; ++i) { write_queue.Enqueue(DEFAULT_PRIORITY, SYN_STREAM, IntToProducer(i), streams[i % arraysize(streams)]); } write_queue.RemovePendingWritesForStreamsAfter(stream1->stream_id()); for (int i = 0; i < 100; i += arraysize(streams)) { SpdyFrameType frame_type = DATA; scoped_ptr<SpdyBufferProducer> frame_producer; base::WeakPtr<SpdyStream> stream; ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)) << "Unable to Dequeue i: " << i; EXPECT_EQ(SYN_STREAM, frame_type); EXPECT_EQ(i, ProducerToInt(frame_producer.Pass())); EXPECT_EQ(stream1, stream.get()); } SpdyFrameType frame_type = DATA; scoped_ptr<SpdyBufferProducer> frame_producer; base::WeakPtr<SpdyStream> stream; EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); } // Enqueue a bunch of writes and then call Clear(). The write queue // should clean up the memory properly, and Dequeue() should return // false. TEST_F(SpdyWriteQueueTest, Clear) { SpdyWriteQueue write_queue; for (int i = 0; i < 100; ++i) { write_queue.Enqueue(DEFAULT_PRIORITY, SYN_STREAM, IntToProducer(i), base::WeakPtr<SpdyStream>()); } write_queue.Clear(); SpdyFrameType frame_type = DATA; scoped_ptr<SpdyBufferProducer> frame_producer; base::WeakPtr<SpdyStream> stream; EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream)); } } // namespace } // namespace net