1 /*
2  * Copyright 2018 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.testing;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static org.junit.Assert.assertSame;
21 import static org.junit.Assert.assertTrue;
22 import static org.mockito.AdditionalAnswers.delegatesTo;
23 import static org.mockito.Matchers.any;
24 import static org.mockito.Matchers.anyLong;
25 import static org.mockito.Mockito.doReturn;
26 import static org.mockito.Mockito.doThrow;
27 import static org.mockito.Mockito.inOrder;
28 import static org.mockito.Mockito.mock;
29 import static org.mockito.Mockito.never;
30 import static org.mockito.Mockito.verify;
31 import static org.mockito.Mockito.verifyNoMoreInteractions;
32 
33 import io.grpc.ManagedChannel;
34 import io.grpc.Server;
35 import io.grpc.internal.FakeClock;
36 import io.grpc.testing.GrpcCleanupRule.Resource;
37 import java.util.concurrent.TimeUnit;
38 import org.junit.Rule;
39 import org.junit.Test;
40 import org.junit.rules.ExpectedException;
41 import org.junit.runner.RunWith;
42 import org.junit.runners.JUnit4;
43 import org.junit.runners.model.MultipleFailureException;
44 import org.junit.runners.model.Statement;
45 import org.mockito.InOrder;
46 
47 /**
48  * Unit tests for {@link GrpcCleanupRule}.
49  */
50 @RunWith(JUnit4.class)
51 public class GrpcCleanupRuleTest {
52   public static final FakeClock fakeClock = new FakeClock();
53 
54   @Rule
55   public ExpectedException thrown = ExpectedException.none();
56 
57   @Test
registerChannelReturnSameChannel()58   public void registerChannelReturnSameChannel() {
59     ManagedChannel channel = mock(ManagedChannel.class);
60     assertSame(channel, new GrpcCleanupRule().register(channel));
61   }
62 
63   @Test
registerServerReturnSameServer()64   public void registerServerReturnSameServer() {
65     Server server = mock(Server.class);
66     assertSame(server, new GrpcCleanupRule().register(server));
67   }
68 
69   @Test
registerNullChannelThrowsNpe()70   public void registerNullChannelThrowsNpe() {
71     ManagedChannel channel = null;
72     GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
73 
74     thrown.expect(NullPointerException.class);
75     thrown.expectMessage("channel");
76 
77     grpcCleanup.register(channel);
78   }
79 
80   @Test
registerNullServerThrowsNpe()81   public void registerNullServerThrowsNpe() {
82     Server server = null;
83     GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
84 
85     thrown.expect(NullPointerException.class);
86     thrown.expectMessage("server");
87 
88     grpcCleanup.register(server);
89   }
90 
91   @Test
singleChannelCleanup()92   public void singleChannelCleanup() throws Throwable {
93     // setup
94     ManagedChannel channel = mock(ManagedChannel.class);
95     Statement statement = mock(Statement.class);
96     InOrder inOrder = inOrder(statement, channel);
97     GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
98 
99     // run
100     grpcCleanup.register(channel);
101 
102     boolean awaitTerminationFailed = false;
103     try {
104       // will throw because channel.awaitTermination(long, TimeUnit) will return false;
105       grpcCleanup.apply(statement, null /* description*/).evaluate();
106     } catch (AssertionError e) {
107       awaitTerminationFailed = true;
108     }
109 
110     // verify
111     assertTrue(awaitTerminationFailed);
112     inOrder.verify(statement).evaluate();
113     inOrder.verify(channel).shutdown();
114     inOrder.verify(channel).awaitTermination(anyLong(), any(TimeUnit.class));
115     inOrder.verify(channel).shutdownNow();
116   }
117 
118   @Test
singleServerCleanup()119   public void singleServerCleanup() throws Throwable {
120     // setup
121     Server server = mock(Server.class);
122     Statement statement = mock(Statement.class);
123     InOrder inOrder = inOrder(statement, server);
124     GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
125 
126     // run
127     grpcCleanup.register(server);
128 
129     boolean awaitTerminationFailed = false;
130     try {
131       // will throw because channel.awaitTermination(long, TimeUnit) will return false;
132       grpcCleanup.apply(statement, null /* description*/).evaluate();
133     } catch (AssertionError e) {
134       awaitTerminationFailed = true;
135     }
136 
137     // verify
138     assertTrue(awaitTerminationFailed);
139     inOrder.verify(statement).evaluate();
140     inOrder.verify(server).shutdown();
141     inOrder.verify(server).awaitTermination(anyLong(), any(TimeUnit.class));
142     inOrder.verify(server).shutdownNow();
143   }
144 
145   @Test
multiResource_cleanupGracefully()146   public void multiResource_cleanupGracefully() throws Throwable {
147     // setup
148     Resource resource1 = mock(Resource.class);
149     Resource resource2 = mock(Resource.class);
150     Resource resource3 = mock(Resource.class);
151     doReturn(true).when(resource1).awaitReleased(anyLong(), any(TimeUnit.class));
152     doReturn(true).when(resource2).awaitReleased(anyLong(), any(TimeUnit.class));
153     doReturn(true).when(resource3).awaitReleased(anyLong(), any(TimeUnit.class));
154 
155     Statement statement = mock(Statement.class);
156     InOrder inOrder = inOrder(statement, resource1, resource2, resource3);
157     GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
158 
159     // run
160     grpcCleanup.register(resource1);
161     grpcCleanup.register(resource2);
162     grpcCleanup.register(resource3);
163     grpcCleanup.apply(statement, null /* description*/).evaluate();
164 
165     // Verify.
166     inOrder.verify(statement).evaluate();
167 
168     inOrder.verify(resource3).cleanUp();
169     inOrder.verify(resource2).cleanUp();
170     inOrder.verify(resource1).cleanUp();
171 
172     inOrder.verify(resource3).awaitReleased(anyLong(), any(TimeUnit.class));
173     inOrder.verify(resource2).awaitReleased(anyLong(), any(TimeUnit.class));
174     inOrder.verify(resource1).awaitReleased(anyLong(), any(TimeUnit.class));
175 
176     inOrder.verifyNoMoreInteractions();
177 
178     verify(resource1, never()).forceCleanUp();
179     verify(resource2, never()).forceCleanUp();
180     verify(resource3, never()).forceCleanUp();
181   }
182 
183   @Test
baseTestFails()184   public void baseTestFails() throws Throwable {
185     // setup
186     Resource resource = mock(Resource.class);
187 
188     Statement statement = mock(Statement.class);
189     doThrow(new Exception()).when(statement).evaluate();
190 
191     GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
192 
193     // run
194     grpcCleanup.register(resource);
195 
196     boolean baseTestFailed = false;
197     try {
198       grpcCleanup.apply(statement, null /* description*/).evaluate();
199     } catch (Exception e) {
200       baseTestFailed = true;
201     }
202 
203     // verify
204     assertTrue(baseTestFailed);
205 
206     verify(resource).forceCleanUp();
207     verifyNoMoreInteractions(resource);
208 
209     verify(resource, never()).cleanUp();
210     verify(resource, never()).awaitReleased(anyLong(), any(TimeUnit.class));
211   }
212 
213   @Test
multiResource_awaitReleasedFails()214   public void multiResource_awaitReleasedFails() throws Throwable {
215     // setup
216     Resource resource1 = mock(Resource.class);
217     Resource resource2 = mock(Resource.class);
218     Resource resource3 = mock(Resource.class);
219     doReturn(true).when(resource1).awaitReleased(anyLong(), any(TimeUnit.class));
220     doReturn(false).when(resource2).awaitReleased(anyLong(), any(TimeUnit.class));
221     doReturn(true).when(resource3).awaitReleased(anyLong(), any(TimeUnit.class));
222 
223     Statement statement = mock(Statement.class);
224     InOrder inOrder = inOrder(statement, resource1, resource2, resource3);
225     GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
226 
227     // run
228     grpcCleanup.register(resource1);
229     grpcCleanup.register(resource2);
230     grpcCleanup.register(resource3);
231 
232     boolean cleanupFailed = false;
233     try {
234       grpcCleanup.apply(statement, null /* description*/).evaluate();
235     } catch (AssertionError e) {
236       cleanupFailed = true;
237     }
238 
239     // verify
240     assertTrue(cleanupFailed);
241 
242     inOrder.verify(statement).evaluate();
243 
244     inOrder.verify(resource3).cleanUp();
245     inOrder.verify(resource2).cleanUp();
246     inOrder.verify(resource1).cleanUp();
247 
248     inOrder.verify(resource3).awaitReleased(anyLong(), any(TimeUnit.class));
249     inOrder.verify(resource2).awaitReleased(anyLong(), any(TimeUnit.class));
250     inOrder.verify(resource2).forceCleanUp();
251     inOrder.verify(resource1).forceCleanUp();
252 
253     inOrder.verifyNoMoreInteractions();
254 
255     verify(resource3, never()).forceCleanUp();
256     verify(resource1, never()).awaitReleased(anyLong(), any(TimeUnit.class));
257   }
258 
259   @Test
multiResource_awaitReleasedInterrupted()260   public void multiResource_awaitReleasedInterrupted() throws Throwable {
261     // setup
262     Resource resource1 = mock(Resource.class);
263     Resource resource2 = mock(Resource.class);
264     Resource resource3 = mock(Resource.class);
265     doReturn(true).when(resource1).awaitReleased(anyLong(), any(TimeUnit.class));
266     doThrow(new InterruptedException())
267         .when(resource2).awaitReleased(anyLong(), any(TimeUnit.class));
268     doReturn(true).when(resource3).awaitReleased(anyLong(), any(TimeUnit.class));
269 
270     Statement statement = mock(Statement.class);
271     InOrder inOrder = inOrder(statement, resource1, resource2, resource3);
272     GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
273 
274     // run
275     grpcCleanup.register(resource1);
276     grpcCleanup.register(resource2);
277     grpcCleanup.register(resource3);
278 
279     boolean cleanupFailed = false;
280     try {
281       grpcCleanup.apply(statement, null /* description*/).evaluate();
282     } catch (InterruptedException e) {
283       cleanupFailed = true;
284     }
285 
286     // verify
287     assertTrue(cleanupFailed);
288     assertTrue(Thread.interrupted());
289 
290     inOrder.verify(statement).evaluate();
291 
292     inOrder.verify(resource3).cleanUp();
293     inOrder.verify(resource2).cleanUp();
294     inOrder.verify(resource1).cleanUp();
295 
296     inOrder.verify(resource3).awaitReleased(anyLong(), any(TimeUnit.class));
297     inOrder.verify(resource2).awaitReleased(anyLong(), any(TimeUnit.class));
298     inOrder.verify(resource2).forceCleanUp();
299     inOrder.verify(resource1).forceCleanUp();
300 
301     inOrder.verifyNoMoreInteractions();
302 
303     verify(resource3, never()).forceCleanUp();
304     verify(resource1, never()).awaitReleased(anyLong(), any(TimeUnit.class));
305   }
306 
307   @Test
multiResource_timeoutCalculation()308   public void multiResource_timeoutCalculation() throws Throwable {
309     // setup
310 
311     Resource resource1 = mock(FakeResource.class,
312         delegatesTo(new FakeResource(1 /* cleanupNanos */, 10 /* awaitReleaseNanos */)));
313 
314     Resource resource2 = mock(FakeResource.class,
315         delegatesTo(new FakeResource(100 /* cleanupNanos */, 1000 /* awaitReleaseNanos */)));
316 
317 
318     Statement statement = mock(Statement.class);
319     InOrder inOrder = inOrder(statement, resource1, resource2);
320     GrpcCleanupRule grpcCleanup = new GrpcCleanupRule().setTicker(fakeClock.getTicker());
321 
322     // run
323     grpcCleanup.register(resource1);
324     grpcCleanup.register(resource2);
325     grpcCleanup.apply(statement, null /* description*/).evaluate();
326 
327     // verify
328     inOrder.verify(statement).evaluate();
329 
330     inOrder.verify(resource2).cleanUp();
331     inOrder.verify(resource1).cleanUp();
332 
333     inOrder.verify(resource2).awaitReleased(
334         TimeUnit.SECONDS.toNanos(10) - 100 - 1, TimeUnit.NANOSECONDS);
335     inOrder.verify(resource1).awaitReleased(
336         TimeUnit.SECONDS.toNanos(10) - 100 - 1 - 1000, TimeUnit.NANOSECONDS);
337 
338     inOrder.verifyNoMoreInteractions();
339 
340     verify(resource2, never()).forceCleanUp();
341     verify(resource1, never()).forceCleanUp();
342   }
343 
344   @Test
multiResource_timeoutCalculation_customTimeout()345   public void multiResource_timeoutCalculation_customTimeout() throws Throwable {
346     // setup
347 
348     Resource resource1 = mock(FakeResource.class,
349         delegatesTo(new FakeResource(1 /* cleanupNanos */, 10 /* awaitReleaseNanos */)));
350 
351     Resource resource2 = mock(FakeResource.class,
352         delegatesTo(new FakeResource(100 /* cleanupNanos */, 1000 /* awaitReleaseNanos */)));
353 
354 
355     Statement statement = mock(Statement.class);
356     InOrder inOrder = inOrder(statement, resource1, resource2);
357     GrpcCleanupRule grpcCleanup = new GrpcCleanupRule()
358         .setTicker(fakeClock.getTicker()).setTimeout(3000, TimeUnit.NANOSECONDS);
359 
360     // run
361     grpcCleanup.register(resource1);
362     grpcCleanup.register(resource2);
363     grpcCleanup.apply(statement, null /* description*/).evaluate();
364 
365     // verify
366     inOrder.verify(statement).evaluate();
367 
368     inOrder.verify(resource2).cleanUp();
369     inOrder.verify(resource1).cleanUp();
370 
371     inOrder.verify(resource2).awaitReleased(3000 - 100 - 1, TimeUnit.NANOSECONDS);
372     inOrder.verify(resource1).awaitReleased(3000 - 100 - 1 - 1000, TimeUnit.NANOSECONDS);
373 
374     inOrder.verifyNoMoreInteractions();
375 
376     verify(resource2, never()).forceCleanUp();
377     verify(resource1, never()).forceCleanUp();
378   }
379 
380   @Test
baseTestFailsThenCleanupFails()381   public void baseTestFailsThenCleanupFails() throws Throwable {
382     // setup
383     Exception baseTestFailure = new Exception();
384 
385     Statement statement = mock(Statement.class);
386     doThrow(baseTestFailure).when(statement).evaluate();
387 
388     Resource resource1 = mock(Resource.class);
389     Resource resource2 = mock(Resource.class);
390     Resource resource3 = mock(Resource.class);
391     doThrow(new RuntimeException()).when(resource2).forceCleanUp();
392 
393     InOrder inOrder = inOrder(statement, resource1, resource2, resource3);
394     GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
395 
396     // run
397     grpcCleanup.register(resource1);
398     grpcCleanup.register(resource2);
399     grpcCleanup.register(resource3);
400 
401     Throwable failure = null;
402     try {
403       grpcCleanup.apply(statement, null /* description*/).evaluate();
404     } catch (Throwable e) {
405       failure = e;
406     }
407 
408     // verify
409     assertThat(failure).isInstanceOf(MultipleFailureException.class);
410     assertSame(baseTestFailure, ((MultipleFailureException) failure).getFailures().get(0));
411 
412     inOrder.verify(statement).evaluate();
413     inOrder.verify(resource3).forceCleanUp();
414     inOrder.verify(resource2).forceCleanUp();
415     inOrder.verifyNoMoreInteractions();
416 
417     verify(resource1, never()).cleanUp();
418     verify(resource2, never()).cleanUp();
419     verify(resource3, never()).cleanUp();
420     verify(resource1, never()).forceCleanUp();
421   }
422 
423   public static class FakeResource implements Resource {
424     private final long cleanupNanos;
425     private final long awaitReleaseNanos;
426 
FakeResource(long cleanupNanos, long awaitReleaseNanos)427     private FakeResource(long cleanupNanos, long awaitReleaseNanos) {
428       this.cleanupNanos = cleanupNanos;
429       this.awaitReleaseNanos = awaitReleaseNanos;
430     }
431 
432     @Override
cleanUp()433     public void cleanUp() {
434       fakeClock.forwardTime(cleanupNanos, TimeUnit.NANOSECONDS);
435     }
436 
437     @Override
forceCleanUp()438     public void forceCleanUp() {
439     }
440 
441     @Override
awaitReleased(long duration, TimeUnit timeUnit)442     public boolean awaitReleased(long duration, TimeUnit timeUnit) {
443       fakeClock.forwardTime(awaitReleaseNanos, TimeUnit.NANOSECONDS);
444       return true;
445     }
446   }
447 }
448