| // Copyright 2020 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.lib.util; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import static java.util.stream.Collectors.toList; | |
| import com.google.devtools.build.lib.server.FailureDetails; | |
| import com.google.devtools.build.lib.server.FailureDetails.Crash; | |
| import com.google.devtools.build.lib.server.FailureDetails.Crash.Code; | |
| import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; | |
| import com.google.protobuf.ProtocolStringList; | |
| import java.util.List; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** Tests for {@link CrashFailureDetails}. */ | |
| @RunWith(JUnit4.class) | |
| public class CrashFailureDetailsTest { | |
| @Test | |
| public void nestedThrowables() { | |
| // This test confirms that throwables' details are recorded: their messages, types, stack | |
| // frames, and causes. The outermost throwable is recorded at index 0. | |
| FailureDetail failureDetail = | |
| CrashFailureDetails.forThrowable( | |
| functionForStackFrameTests_A(functionForStackFrameTests_B())); | |
| assertThat(failureDetail.getMessage()).isEqualTo("Crashed: myMessage_A, myMessage_B"); | |
| assertThat(failureDetail.hasCrash()).isTrue(); | |
| Crash crash = failureDetail.getCrash(); | |
| assertThat(crash.getCode()).isEqualTo(Code.CRASH_UNKNOWN); | |
| assertThat(crash.getCausesCount()).isEqualTo(2); | |
| FailureDetails.Throwable outerCause = crash.getCauses(0); | |
| assertThat(outerCause.getMessage()).isEqualTo("myMessage_A"); | |
| assertThat(outerCause.getThrowableClass()) | |
| .isEqualTo("com.google.devtools.build.lib.util.CrashFailureDetailsTest$TestException"); | |
| assertThat(outerCause.getStackTraceCount()).isAtLeast(2); | |
| assertThat(outerCause.getStackTrace(0)) | |
| .contains( | |
| "com.google.devtools.build.lib.util.CrashFailureDetailsTest." | |
| + "functionForStackFrameTests_A"); | |
| assertThat(outerCause.getStackTrace(1)) | |
| .contains("com.google.devtools.build.lib.util.CrashFailureDetailsTest.nestedThrowables"); | |
| FailureDetails.Throwable innerCause = crash.getCauses(1); | |
| assertThat(innerCause.getMessage()).isEqualTo("myMessage_B"); | |
| assertThat(innerCause.getThrowableClass()) | |
| .isEqualTo("com.google.devtools.build.lib.util.CrashFailureDetailsTest$TestException"); | |
| assertThat(innerCause.getStackTraceCount()).isAtLeast(2); | |
| assertThat(innerCause.getStackTrace(0)) | |
| .contains( | |
| "com.google.devtools.build.lib.util.CrashFailureDetailsTest." | |
| + "functionForStackFrameTests_B"); | |
| assertThat(innerCause.getStackTrace(1)) | |
| .contains("com.google.devtools.build.lib.util.CrashFailureDetailsTest.nestedThrowables"); | |
| } | |
| @Test | |
| public void causeLimit() { | |
| // This test confirms that at most 5 throwables are recorded. | |
| TestException inner5 = new TestException("inner5"); | |
| TestException inner4 = new TestException("inner4", inner5); | |
| TestException inner3 = new TestException("inner3", inner4); | |
| TestException inner2 = new TestException("inner2", inner3); | |
| TestException inner1 = new TestException("inner1", inner2); | |
| TestException outer = new TestException("outer", inner1); | |
| assertThat(CrashFailureDetails.forThrowable(outer).getCrash().getCausesCount()).isEqualTo(5); | |
| } | |
| @Test | |
| public void causeCycle() { | |
| // This test confirms that throwables in a cause cycle are visited at most once. | |
| TestException inner2 = new TestException("inner2"); | |
| TestException inner1 = new TestException("inner1", inner2); | |
| TestException outer = new TestException("outer", inner1); | |
| inner2.initCause(inner1); | |
| List<FailureDetails.Throwable> causesList = | |
| CrashFailureDetails.forThrowable(outer).getCrash().getCausesList(); | |
| assertThat(causesList.stream().map(FailureDetails.Throwable::getMessage).collect(toList())) | |
| .containsExactly("outer", "inner1", "inner2"); | |
| } | |
| @Test | |
| public void deepStack() { | |
| ProtocolStringList stackTraceList = | |
| CrashFailureDetails.forThrowable(functionForDeepStackTrace(1001)) | |
| .getCrash() | |
| .getCauses(0) | |
| .getStackTraceList(); | |
| assertThat(stackTraceList).hasSize(1000); | |
| // Check that the deepest 1000 frames were recorded: | |
| for (String stackFrame : stackTraceList) { | |
| assertThat(stackFrame).contains("CrashFailureDetailsTest.functionForDeepStackTrace"); | |
| } | |
| } | |
| @Test | |
| public void detailedExitConstruction() { | |
| assertThat( | |
| CrashFailureDetails.detailedExitCodeForThrowable(new OutOfMemoryError()).getExitCode()) | |
| .isEqualTo(ExitCode.OOM_ERROR); | |
| assertThat( | |
| CrashFailureDetails.detailedExitCodeForThrowable(new InterruptedException()) | |
| .getExitCode()) | |
| .isEqualTo(ExitCode.BLAZE_INTERNAL_ERROR); | |
| } | |
| private static TestException functionForStackFrameTests_A(TestException cause) { | |
| return new TestException("myMessage_A", cause); | |
| } | |
| private static TestException functionForStackFrameTests_B() { | |
| return new TestException("myMessage_B"); | |
| } | |
| private static TestException functionForDeepStackTrace(int framesToBuild) { | |
| if (framesToBuild <= 1) { | |
| return new TestException("myMessage_deep"); | |
| } else { | |
| return functionForDeepStackTrace(framesToBuild - 1); | |
| } | |
| } | |
| private static class TestException extends Exception { | |
| private TestException(String message) { | |
| super(message); | |
| } | |
| private TestException(String message, Throwable cause) { | |
| super(message, cause); | |
| } | |
| } | |
| } |