• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 Google LLC
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google LLC nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 package com.google.api.gax.rpc;
31 
32 import static com.google.common.truth.Truth.assertThat;
33 
34 import com.google.api.core.ApiFutures;
35 import com.google.api.core.ListenableFutureToApiFuture;
36 import com.google.api.gax.rpc.testing.FakeStatusCode;
37 import com.google.common.util.concurrent.ListenableFuture;
38 import com.google.common.util.concurrent.ListeningExecutorService;
39 import com.google.common.util.concurrent.MoreExecutors;
40 import com.google.common.util.concurrent.UncheckedExecutionException;
41 import java.io.IOException;
42 import java.util.concurrent.Executors;
43 import org.junit.Assert;
44 import org.junit.Test;
45 import org.junit.runner.RunWith;
46 import org.junit.runners.JUnit4;
47 
48 @RunWith(JUnit4.class)
49 public class ApiExceptionsTest {
50 
51   @Test
noException()52   public void noException() {
53     Integer result = ApiExceptions.callAndTranslateApiException(ApiFutures.immediateFuture(2));
54     assertThat(result).isEqualTo(2);
55   }
56 
57   @Test
throwsApiException()58   public void throwsApiException() {
59     Exception throwable =
60         new UnavailableException(null, FakeStatusCode.of(StatusCode.Code.UNAVAILABLE), false);
61     try {
62       ApiExceptions.callAndTranslateApiException(ApiFutures.immediateFailedFuture(throwable));
63       Assert.fail("ApiExceptions should have thrown an exception");
64     } catch (ApiException expected) {
65       assertThat(expected).isSameInstanceAs(throwable);
66     }
67   }
68 
69   @Test
throwsIOException()70   public void throwsIOException() {
71     try {
72       ApiExceptions.callAndTranslateApiException(
73           ApiFutures.immediateFailedFuture(new IOException()));
74       Assert.fail("ApiExceptions should have thrown an exception");
75     } catch (UncheckedExecutionException expected) {
76       assertThat(expected).hasCauseThat().isInstanceOf(IOException.class);
77     }
78   }
79 
80   @Test
throwsRuntimeException()81   public void throwsRuntimeException() {
82     try {
83       ApiExceptions.callAndTranslateApiException(
84           ApiFutures.immediateFailedFuture(new IllegalArgumentException()));
85       Assert.fail("ApiExceptions should have thrown an exception");
86     } catch (IllegalArgumentException expected) {
87       assertThat(expected).isInstanceOf(IllegalArgumentException.class);
88     }
89   }
90 
91   /**
92    * Make sure that the caller's stacktrace is preserved when the future is unwrapped. The
93    * stacktrace will be preserved as a suppressed RuntimeException.
94    */
95   @Test
containsCurrentStacktrace()96   public void containsCurrentStacktrace() {
97     final String currentMethod = "containsCurrentStacktrace";
98 
99     // Throw an error in an executor, which will cause it to lose the current stack frame
100     ListeningExecutorService executor =
101         MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
102 
103     ListenableFuture<?> futureError =
104         executor.submit(
105             new Runnable() {
106               @Override
107               public void run() {
108                 throw new IllegalArgumentException();
109               }
110             });
111     ListenableFutureToApiFuture<?> futureErrorWrapper =
112         new ListenableFutureToApiFuture<>(futureError);
113     executor.shutdown();
114 
115     // Unwrap the future
116     Exception actualError = null;
117     try {
118       ApiExceptions.callAndTranslateApiException(futureErrorWrapper);
119     } catch (Exception e) {
120       actualError = e;
121     }
122 
123     // Sanity check that the current stack trace is not in the exception
124     assertThat(actualError).isNotNull();
125     assertThat(isMethodInStacktrace(currentMethod, actualError)).isFalse();
126 
127     // Verify that it is preserved as a suppressed exception.
128     assertThat(actualError.getSuppressed()[0]).isInstanceOf(AsyncTaskException.class);
129     assertThat(isMethodInStacktrace(currentMethod, actualError.getSuppressed()[0])).isTrue();
130   }
131 
isMethodInStacktrace(String method, Throwable t)132   private static boolean isMethodInStacktrace(String method, Throwable t) {
133     for (StackTraceElement e : t.getStackTrace()) {
134       if (method.equals(e.getMethodName())) {
135         return true;
136       }
137     }
138 
139     return false;
140   }
141 }
142