1package com.squareup.okhttp;
2
3import com.squareup.okhttp.Call.AsyncCall;
4import java.util.ArrayList;
5import java.util.Arrays;
6import java.util.Iterator;
7import java.util.List;
8import java.util.concurrent.AbstractExecutorService;
9import java.util.concurrent.TimeUnit;
10import org.junit.Before;
11import org.junit.Test;
12
13import static org.junit.Assert.assertEquals;
14import static org.junit.Assert.fail;
15
16public final class DispatcherTest {
17  RecordingExecutor executor = new RecordingExecutor();
18  RecordingCallback callback = new RecordingCallback();
19  Dispatcher dispatcher = new Dispatcher(executor);
20  OkHttpClient client = new OkHttpClient().setDispatcher(dispatcher);
21
22  @Before public void setUp() throws Exception {
23    dispatcher.setMaxRequests(20);
24    dispatcher.setMaxRequestsPerHost(10);
25  }
26
27  @Test public void maxRequestsZero() throws Exception {
28    try {
29      dispatcher.setMaxRequests(0);
30      fail();
31    } catch (IllegalArgumentException expected) {
32    }
33  }
34
35  @Test public void maxPerHostZero() throws Exception {
36    try {
37      dispatcher.setMaxRequestsPerHost(0);
38      fail();
39    } catch (IllegalArgumentException expected) {
40    }
41  }
42
43  @Test public void enqueuedJobsRunImmediately() throws Exception {
44    client.newCall(newRequest("http://a/1")).enqueue(callback);
45    executor.assertJobs("http://a/1");
46  }
47
48  @Test public void maxRequestsEnforced() throws Exception {
49    dispatcher.setMaxRequests(3);
50    client.newCall(newRequest("http://a/1")).enqueue(callback);
51    client.newCall(newRequest("http://a/2")).enqueue(callback);
52    client.newCall(newRequest("http://b/1")).enqueue(callback);
53    client.newCall(newRequest("http://b/2")).enqueue(callback);
54    executor.assertJobs("http://a/1", "http://a/2", "http://b/1");
55  }
56
57  @Test public void maxPerHostEnforced() throws Exception {
58    dispatcher.setMaxRequestsPerHost(2);
59    client.newCall(newRequest("http://a/1")).enqueue(callback);
60    client.newCall(newRequest("http://a/2")).enqueue(callback);
61    client.newCall(newRequest("http://a/3")).enqueue(callback);
62    executor.assertJobs("http://a/1", "http://a/2");
63  }
64
65  @Test public void increasingMaxRequestsPromotesJobsImmediately() throws Exception {
66    dispatcher.setMaxRequests(2);
67    client.newCall(newRequest("http://a/1")).enqueue(callback);
68    client.newCall(newRequest("http://b/1")).enqueue(callback);
69    client.newCall(newRequest("http://c/1")).enqueue(callback);
70    client.newCall(newRequest("http://a/2")).enqueue(callback);
71    client.newCall(newRequest("http://b/2")).enqueue(callback);
72    dispatcher.setMaxRequests(4);
73    executor.assertJobs("http://a/1", "http://b/1", "http://c/1", "http://a/2");
74  }
75
76  @Test public void increasingMaxPerHostPromotesJobsImmediately() throws Exception {
77    dispatcher.setMaxRequestsPerHost(2);
78    client.newCall(newRequest("http://a/1")).enqueue(callback);
79    client.newCall(newRequest("http://a/2")).enqueue(callback);
80    client.newCall(newRequest("http://a/3")).enqueue(callback);
81    client.newCall(newRequest("http://a/4")).enqueue(callback);
82    client.newCall(newRequest("http://a/5")).enqueue(callback);
83    dispatcher.setMaxRequestsPerHost(4);
84    executor.assertJobs("http://a/1", "http://a/2", "http://a/3", "http://a/4");
85  }
86
87  @Test public void oldJobFinishesNewJobCanRunDifferentHost() throws Exception {
88    dispatcher.setMaxRequests(1);
89    client.newCall(newRequest("http://a/1")).enqueue(callback);
90    client.newCall(newRequest("http://b/1")).enqueue(callback);
91    executor.finishJob("http://a/1");
92    executor.assertJobs("http://b/1");
93  }
94
95  @Test public void oldJobFinishesNewJobWithSameHostStarts() throws Exception {
96    dispatcher.setMaxRequests(2);
97    dispatcher.setMaxRequestsPerHost(1);
98    client.newCall(newRequest("http://a/1")).enqueue(callback);
99    client.newCall(newRequest("http://b/1")).enqueue(callback);
100    client.newCall(newRequest("http://b/2")).enqueue(callback);
101    client.newCall(newRequest("http://a/2")).enqueue(callback);
102    executor.finishJob("http://a/1");
103    executor.assertJobs("http://b/1", "http://a/2");
104  }
105
106  @Test public void oldJobFinishesNewJobCantRunDueToHostLimit() throws Exception {
107    dispatcher.setMaxRequestsPerHost(1);
108    client.newCall(newRequest("http://a/1")).enqueue(callback);
109    client.newCall(newRequest("http://b/1")).enqueue(callback);
110    client.newCall(newRequest("http://a/2")).enqueue(callback);
111    executor.finishJob("http://b/1");
112    executor.assertJobs("http://a/1");
113  }
114
115  @Test public void cancelingRunningJobTakesNoEffectUntilJobFinishes() throws Exception {
116    dispatcher.setMaxRequests(1);
117    client.newCall(newRequest("http://a/1", "tag1")).enqueue(callback);
118    client.newCall(newRequest("http://a/2")).enqueue(callback);
119    dispatcher.cancel("tag1");
120    executor.assertJobs("http://a/1");
121    executor.finishJob("http://a/1");
122    executor.assertJobs("http://a/2");
123  }
124
125  class RecordingExecutor extends AbstractExecutorService {
126    private List<AsyncCall> calls = new ArrayList<>();
127
128    @Override public void execute(Runnable command) {
129      calls.add((AsyncCall) command);
130    }
131
132    public void assertJobs(String... expectedUrls) {
133      List<String> actualUrls = new ArrayList<>();
134      for (AsyncCall call : calls) {
135        actualUrls.add(call.request().urlString());
136      }
137      assertEquals(Arrays.asList(expectedUrls), actualUrls);
138    }
139
140    public void finishJob(String url) {
141      for (Iterator<AsyncCall> i = calls.iterator(); i.hasNext(); ) {
142        AsyncCall call = i.next();
143        if (call.request().urlString().equals(url)) {
144          i.remove();
145          dispatcher.finished(call);
146          return;
147        }
148      }
149      throw new AssertionError("No such job: " + url);
150    }
151
152    @Override public void shutdown() {
153      throw new UnsupportedOperationException();
154    }
155
156    @Override public List<Runnable> shutdownNow() {
157      throw new UnsupportedOperationException();
158    }
159
160    @Override public boolean isShutdown() {
161      throw new UnsupportedOperationException();
162    }
163
164    @Override public boolean isTerminated() {
165      throw new UnsupportedOperationException();
166    }
167
168    @Override public boolean awaitTermination(long timeout, TimeUnit unit)
169        throws InterruptedException {
170      throw new UnsupportedOperationException();
171    }
172  }
173
174  private Request newRequest(String url) {
175    return new Request.Builder().url(url).build();
176  }
177
178  private Request newRequest(String url, String tag) {
179    return new Request.Builder().url(url).tag(tag).build();
180  }
181}
182