// Copyright 2017 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.android.desugar.runtime; import static com.google.common.truth.Truth.assertThat; import static com.google.devtools.build.android.desugar.runtime.ThrowableExtension.MimicDesugaringStrategy.SUPPRESSED_PREFIX; import static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.getTwrStrategyClassNameSpecifiedInSystemProperty; import static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.isNullStrategy; import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.fail; import com.google.devtools.build.android.desugar.runtime.ThrowableExtension.MimicDesugaringStrategy; import com.google.devtools.build.android.desugar.runtime.ThrowableExtension.NullDesugaringStrategy; import com.google.devtools.build.android.desugar.runtime.ThrowableExtension.ReuseDesugaringStrategy; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.PrintWriter; import java.util.function.Consumer; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Test case for {@link ThrowableExtension} */ @RunWith(JUnit4.class) public class ThrowableExtensionTest { /** * This test tests the behavior of closing resources via reflection. This is only enabled below * API 19. So, if the API level is 19 or above, this test will simply skip. */ @Test public void testCloseResourceViaReflection() throws Throwable { class Resource extends AbstractResource { protected Resource(boolean exceptionOnClose) { super(exceptionOnClose); } public void close() throws Exception { super.internalClose(); } } if (ThrowableExtension.API_LEVEL >= 19) { return; } { Resource r = new Resource(false); assertThat(r.isClosed()).isFalse(); ThrowableExtension.closeResource(null, r); assertThat(r.isClosed()).isTrue(); } { Resource r = new Resource(true); assertThat(r.isClosed()).isFalse(); assertThrows(IOException.class, () -> ThrowableExtension.closeResource(null, r)); } { Resource r = new Resource(false); assertThat(r.isClosed()).isFalse(); ThrowableExtension.closeResource(new Exception(), r); assertThat(r.isClosed()).isTrue(); } { Resource r = new Resource(true); assertThat(r.isClosed()).isFalse(); assertThrows(Exception.class, () -> ThrowableExtension.closeResource(new Exception(), r)); } } /** * Test the new method closeResources() in the runtime library. * *
The method is introduced to fix b/37167433. */ @Test public void testCloseResource() throws Throwable { /** * A resource implementing the interface AutoCloseable. This interface is only available since * API 19. */ class AutoCloseableResource extends AbstractResource implements AutoCloseable { protected AutoCloseableResource(boolean exceptionOnClose) { super(exceptionOnClose); } @Override public void close() throws Exception { internalClose(); } } /** A resource implementing the interface Closeable. */ class CloseableResource extends AbstractResource implements Closeable { protected CloseableResource(boolean exceptionOnClose) { super(exceptionOnClose); } @Override public void close() throws IOException { internalClose(); } } { CloseableResource r = new CloseableResource(false); assertThat(r.isClosed()).isFalse(); ThrowableExtension.closeResource(null, r); assertThat(r.isClosed()).isTrue(); } { CloseableResource r = new CloseableResource(false); assertThat(r.isClosed()).isFalse(); Exception suppressor = new Exception(); ThrowableExtension.closeResource(suppressor, r); assertThat(r.isClosed()).isTrue(); assertThat(ThrowableExtension.getSuppressed(suppressor)).isEmpty(); } { CloseableResource r = new CloseableResource(true); assertThat(r.isClosed()).isFalse(); assertThrows(IOException.class, () -> ThrowableExtension.closeResource(null, r)); assertThat(r.isClosed()).isFalse(); } { CloseableResource r = new CloseableResource(true); assertThat(r.isClosed()).isFalse(); Exception suppressor = new Exception(); assertThrows(Exception.class, () -> ThrowableExtension.closeResource(suppressor, r)); assertThat(r.isClosed()).isFalse(); // Failed to close. if (!isNullStrategy()) { assertThat(ThrowableExtension.getSuppressed(suppressor)).hasLength(1); assertThat(ThrowableExtension.getSuppressed(suppressor)[0].getClass()) .isEqualTo(IOException.class); } } { AutoCloseableResource r = new AutoCloseableResource(false); assertThat(r.isClosed()).isFalse(); ThrowableExtension.closeResource(null, r); assertThat(r.isClosed()).isTrue(); } { AutoCloseableResource r = new AutoCloseableResource(false); assertThat(r.isClosed()).isFalse(); Exception suppressor = new Exception(); ThrowableExtension.closeResource(suppressor, r); assertThat(r.isClosed()).isTrue(); assertThat(ThrowableExtension.getSuppressed(suppressor)).isEmpty(); } { AutoCloseableResource r = new AutoCloseableResource(true); assertThat(r.isClosed()).isFalse(); assertThrows(IOException.class, () -> ThrowableExtension.closeResource(null, r)); assertThat(r.isClosed()).isFalse(); } { AutoCloseableResource r = new AutoCloseableResource(true); assertThat(r.isClosed()).isFalse(); Exception suppressor = new Exception(); assertThrows(Exception.class, () -> ThrowableExtension.closeResource(suppressor, r)); assertThat(r.isClosed()).isFalse(); // Failed to close. if (!isNullStrategy()) { assertThat(ThrowableExtension.getSuppressed(suppressor)).hasLength(1); assertThat(ThrowableExtension.getSuppressed(suppressor)[0].getClass()) .isEqualTo(IOException.class); } assertThat(r.isClosed()).isFalse(); } } /** * LightweightStackTraceRecorder tracks the calls of various printStackTrace(*), and ensures that * *
suppressed exceptions are printed only once.
*/
@Test
public void testLightweightStackTraceRecorder() throws IOException {
MimicDesugaringStrategy strategy = new MimicDesugaringStrategy();
ExceptionForTest receiver = new ExceptionForTest(strategy);
FileNotFoundException suppressed = new FileNotFoundException();
strategy.addSuppressed(receiver, suppressed);
String trace = printStackTraceStderrToString(() -> strategy.printStackTrace(receiver));
assertThat(trace).contains(SUPPRESSED_PREFIX);
assertThat(countOccurrences(trace, SUPPRESSED_PREFIX)).isEqualTo(1);
}
@Test
public void testMimicDesugaringStrategy() throws IOException {
MimicDesugaringStrategy strategy = new MimicDesugaringStrategy();
IOException receiver = new IOException();
FileNotFoundException suppressed = new FileNotFoundException();
strategy.addSuppressed(receiver, suppressed);
assertThat(
printStackTracePrintStreamToString(
stream -> strategy.printStackTrace(receiver, stream)))
.contains(SUPPRESSED_PREFIX);
assertThat(
printStackTracePrintWriterToString(
writer -> strategy.printStackTrace(receiver, writer)))
.contains(SUPPRESSED_PREFIX);
assertThat(printStackTraceStderrToString(() -> strategy.printStackTrace(receiver)))
.contains(SUPPRESSED_PREFIX);
}
private void testThrowableExtensionWithMimicDesugaringStrategy() throws IOException {
IOException receiver = new IOException();
FileNotFoundException suppressed = new FileNotFoundException();
ThrowableExtension.addSuppressed(receiver, suppressed);
assertThat(
printStackTracePrintStreamToString(
stream -> ThrowableExtension.printStackTrace(receiver, stream)))
.contains(SUPPRESSED_PREFIX);
assertThat(
printStackTracePrintWriterToString(
writer -> ThrowableExtension.printStackTrace(receiver, writer)))
.contains(SUPPRESSED_PREFIX);
assertThat(printStackTraceStderrToString(() -> ThrowableExtension.printStackTrace(receiver)))
.contains(SUPPRESSED_PREFIX);
}
private interface PrintStackTraceCaller {
void printStackTrace();
}
private static String printStackTraceStderrToString(PrintStackTraceCaller caller)
throws IOException {
PrintStream err = System.err;
try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
PrintStream newErr = new PrintStream(stream);
System.setErr(newErr);
caller.printStackTrace();
newErr.flush();
return stream.toString();
} finally {
System.setErr(err);
}
}
private static String printStackTracePrintStreamToString(Consumer