1 /*
2  * Copyright 2015 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc;
18 
19 import static com.google.common.truth.Truth.assertAbout;
20 import static com.google.common.truth.Truth.assertThat;
21 import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
22 import static io.grpc.testing.DeadlineSubject.deadline;
23 import static java.util.concurrent.TimeUnit.MILLISECONDS;
24 import static java.util.concurrent.TimeUnit.MINUTES;
25 import static java.util.concurrent.TimeUnit.NANOSECONDS;
26 import static java.util.concurrent.TimeUnit.SECONDS;
27 import static org.junit.Assert.fail;
28 import static org.mockito.Mockito.mock;
29 
30 import com.google.common.base.Objects;
31 import io.grpc.internal.SerializingExecutor;
32 import java.util.concurrent.Executor;
33 import java.util.concurrent.TimeUnit;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 import org.junit.runners.JUnit4;
37 
38 /** Unit tests for {@link CallOptions}. */
39 @RunWith(JUnit4.class)
40 public class CallOptionsTest {
41   private static final CallOptions.Key<String> OPTION_1
42       = CallOptions.Key.createWithDefault("option1", "default");
43   private static final CallOptions.Key<String> OPTION_2
44       = CallOptions.Key.createWithDefault("option2", "default");
45   private final String sampleAuthority = "authority";
46   private final String sampleCompressor = "compressor";
47   private final Deadline.Ticker ticker = new FakeTicker();
48   private final Deadline sampleDeadline = Deadline.after(1, NANOSECONDS, ticker);
49   private final CallCredentials sampleCreds = mock(CallCredentials.class);
50   private final ClientStreamTracer.Factory tracerFactory1 = new FakeTracerFactory("tracerFactory1");
51   private final ClientStreamTracer.Factory tracerFactory2 = new FakeTracerFactory("tracerFactory2");
52   private final CallOptions allSet = CallOptions.DEFAULT
53       .withAuthority(sampleAuthority)
54       .withDeadline(sampleDeadline)
55       .withCallCredentials(sampleCreds)
56       .withCompression(sampleCompressor)
57       .withWaitForReady()
58       .withExecutor(directExecutor())
59       .withOption(OPTION_1, "value1")
60       .withStreamTracerFactory(tracerFactory1)
61       .withOption(OPTION_2, "value2")
62       .withStreamTracerFactory(tracerFactory2);
63 
64   @Test
defaultsAreAllNull()65   public void defaultsAreAllNull() {
66     assertThat(CallOptions.DEFAULT.getDeadline()).isNull();
67     assertThat(CallOptions.DEFAULT.getAuthority()).isNull();
68     assertThat(CallOptions.DEFAULT.getExecutor()).isNull();
69     assertThat(CallOptions.DEFAULT.getCredentials()).isNull();
70     assertThat(CallOptions.DEFAULT.getCompressor()).isNull();
71     assertThat(CallOptions.DEFAULT.isWaitForReady()).isFalse();
72     assertThat(CallOptions.DEFAULT.getStreamTracerFactories()).isEmpty();
73   }
74 
75   @Test
withAndWithoutWaitForReady()76   public void withAndWithoutWaitForReady() {
77     assertThat(CallOptions.DEFAULT.withWaitForReady().isWaitForReady()).isTrue();
78     assertThat(CallOptions.DEFAULT.withWaitForReady().withoutWaitForReady().isWaitForReady())
79         .isFalse();
80   }
81 
82   @Test
allWiths()83   public void allWiths() {
84     assertThat(allSet.getAuthority()).isSameAs(sampleAuthority);
85     assertThat(allSet.getDeadline()).isSameAs(sampleDeadline);
86     assertThat(allSet.getCredentials()).isSameAs(sampleCreds);
87     assertThat(allSet.getCompressor()).isSameAs(sampleCompressor);
88     assertThat(allSet.getExecutor()).isSameAs(directExecutor());
89     assertThat(allSet.getOption(OPTION_1)).isSameAs("value1");
90     assertThat(allSet.getOption(OPTION_2)).isSameAs("value2");
91     assertThat(allSet.isWaitForReady()).isTrue();
92   }
93 
94   @Test
noStrayModifications()95   public void noStrayModifications() {
96     assertThat(equal(allSet, allSet.withAuthority("blah").withAuthority(sampleAuthority)))
97         .isTrue();
98     assertThat(
99         equal(allSet,
100             allSet.withDeadline(Deadline.after(314, NANOSECONDS)).withDeadline(sampleDeadline)))
101         .isTrue();
102     assertThat(
103         equal(allSet,
104             allSet.withCallCredentials(mock(CallCredentials.class))
105             .withCallCredentials(sampleCreds)))
106         .isTrue();
107   }
108 
109   @Test
mutation()110   public void mutation() {
111     Deadline deadline = Deadline.after(10, SECONDS);
112     CallOptions options1 = CallOptions.DEFAULT.withDeadline(deadline);
113     assertThat(CallOptions.DEFAULT.getDeadline()).isNull();
114     assertThat(deadline).isSameAs(options1.getDeadline());
115 
116     CallOptions options2 = options1.withDeadline(null);
117     assertThat(deadline).isSameAs(options1.getDeadline());
118     assertThat(options2.getDeadline()).isNull();
119   }
120 
121   @Test
mutateExecutor()122   public void mutateExecutor() {
123     Executor executor = directExecutor();
124     CallOptions options1 = CallOptions.DEFAULT.withExecutor(executor);
125     assertThat(CallOptions.DEFAULT.getExecutor()).isNull();
126     assertThat(executor).isSameAs(options1.getExecutor());
127 
128     CallOptions options2 = options1.withExecutor(null);
129     assertThat(executor).isSameAs(options1.getExecutor());
130     assertThat(options2.getExecutor()).isNull();
131   }
132 
133   @Test
withDeadlineAfter()134   public void withDeadlineAfter() {
135     Deadline actual = CallOptions.DEFAULT.withDeadlineAfter(1, MINUTES).getDeadline();
136     Deadline expected = Deadline.after(1, MINUTES);
137 
138     assertAbout(deadline()).that(actual).isWithin(10, MILLISECONDS).of(expected);
139   }
140 
141   @Test
toStringMatches_noDeadline_default()142   public void toStringMatches_noDeadline_default() {
143     String actual = allSet
144         .withDeadline(null)
145         .withExecutor(new SerializingExecutor(directExecutor()))
146         .withCallCredentials(null)
147         .withMaxInboundMessageSize(44)
148         .withMaxOutboundMessageSize(55)
149         .toString();
150 
151     assertThat(actual).contains("deadline=null");
152     assertThat(actual).contains("authority=authority");
153     assertThat(actual).contains("callCredentials=null");
154     assertThat(actual).contains("executor=class io.grpc.internal.SerializingExecutor");
155     assertThat(actual).contains("compressorName=compressor");
156     assertThat(actual).contains("customOptions=[[option1, value1], [option2, value2]]");
157     assertThat(actual).contains("waitForReady=true");
158     assertThat(actual).contains("maxInboundMessageSize=44");
159     assertThat(actual).contains("maxOutboundMessageSize=55");
160     assertThat(actual).contains("streamTracerFactories=[tracerFactory1, tracerFactory2]");
161   }
162 
163   @Test
toStringMatches_noDeadline()164   public void toStringMatches_noDeadline() {
165     String actual = CallOptions.DEFAULT.toString();
166     assertThat(actual).contains("deadline=null");
167   }
168 
169   @Test
toStringMatches_withDeadline()170   public void toStringMatches_withDeadline() {
171     assertThat(allSet.toString()).contains("1 ns from now");
172   }
173 
174   @Test
withCustomOptionDefault()175   public void withCustomOptionDefault() {
176     CallOptions opts = CallOptions.DEFAULT;
177     assertThat(opts.getOption(OPTION_1)).isEqualTo("default");
178   }
179 
180   @Test
withCustomOption()181   public void withCustomOption() {
182     CallOptions opts = CallOptions.DEFAULT.withOption(OPTION_1, "v1");
183     assertThat(opts.getOption(OPTION_1)).isEqualTo("v1");
184   }
185 
186   @Test
withCustomOptionLastOneWins()187   public void withCustomOptionLastOneWins() {
188     CallOptions opts = CallOptions.DEFAULT.withOption(OPTION_1, "v1").withOption(OPTION_1, "v2");
189     assertThat(opts.getOption(OPTION_1)).isEqualTo("v2");
190   }
191 
192   @Test
withMultipleCustomOption()193   public void withMultipleCustomOption() {
194     CallOptions opts = CallOptions.DEFAULT.withOption(OPTION_1, "v1").withOption(OPTION_2, "v2");
195     assertThat(opts.getOption(OPTION_1)).isEqualTo("v1");
196     assertThat(opts.getOption(OPTION_2)).isEqualTo("v2");
197   }
198 
199   @Test
withStreamTracerFactory()200   public void withStreamTracerFactory() {
201     CallOptions opts1 = CallOptions.DEFAULT.withStreamTracerFactory(tracerFactory1);
202     CallOptions opts2 = opts1.withStreamTracerFactory(tracerFactory2);
203     CallOptions opts3 = opts2.withStreamTracerFactory(tracerFactory2);
204 
205     assertThat(opts1.getStreamTracerFactories()).containsExactly(tracerFactory1);
206     assertThat(opts2.getStreamTracerFactories()).containsExactly(tracerFactory1, tracerFactory2)
207         .inOrder();
208     assertThat(opts3.getStreamTracerFactories())
209         .containsExactly(tracerFactory1, tracerFactory2, tracerFactory2).inOrder();
210 
211     try {
212       CallOptions.DEFAULT.getStreamTracerFactories().add(tracerFactory1);
213       fail("Should have thrown. The list should be unmodifiable.");
214     } catch (UnsupportedOperationException e) {
215       // Expected
216     }
217 
218     try {
219       opts2.getStreamTracerFactories().clear();
220       fail("Should have thrown. The list should be unmodifiable.");
221     } catch (UnsupportedOperationException e) {
222       // Expected
223     }
224   }
225 
226   // Only used in noStrayModifications()
227   // TODO(carl-mastrangelo): consider making a CallOptionsSubject for Truth.
equal(CallOptions o1, CallOptions o2)228   private static boolean equal(CallOptions o1, CallOptions o2) {
229     return Objects.equal(o1.getDeadline(), o2.getDeadline())
230         && Objects.equal(o1.getAuthority(), o2.getAuthority())
231         && Objects.equal(o1.getCredentials(), o2.getCredentials());
232   }
233 
234   private static class FakeTicker extends Deadline.Ticker {
235     private long time;
236 
237     @Override
read()238     public long read() {
239       return time;
240     }
241 
reset(long time)242     public void reset(long time) {
243       this.time = time;
244     }
245 
increment(long period, TimeUnit unit)246     public void increment(long period, TimeUnit unit) {
247       if (period < 0) {
248         throw new IllegalArgumentException();
249       }
250       this.time += unit.toNanos(period);
251     }
252   }
253 
254   private static class FakeTracerFactory extends ClientStreamTracer.Factory {
255     final String name;
256 
FakeTracerFactory(String name)257     FakeTracerFactory(String name) {
258       this.name = name;
259     }
260 
261     @Override
newClientStreamTracer(CallOptions callOptions, Metadata headers)262     public ClientStreamTracer newClientStreamTracer(CallOptions callOptions, Metadata headers) {
263       return new ClientStreamTracer() {};
264     }
265 
266     @Override
toString()267     public String toString() {
268       return name;
269     }
270   }
271 }
272