blob: 7fe505b5b56685582eaccd8af5363a3bfa9364bc [file] [log] [blame]
// Copyright 2014 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.skyframe;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.devtools.build.skyframe.SkyFunctionException.ReifiedSkyFunctionException;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for the non-trivial creation logic of {@link ErrorInfo}. */
@RunWith(JUnit4.class)
public final class ErrorInfoTest {
/** Dummy SkyFunctionException implementation for the sake of testing. */
private static final class DummySkyFunctionException extends SkyFunctionException {
private final boolean isCatastrophic;
DummySkyFunctionException(Exception cause, boolean isTransient, boolean isCatastrophic) {
super(cause, isTransient ? Transience.TRANSIENT : Transience.PERSISTENT);
this.isCatastrophic = isCatastrophic;
}
@Override
public boolean isCatastrophic() {
return isCatastrophic;
}
}
private static void runTestFromException(
boolean isDirectlyTransient, boolean isTransitivelyTransient) {
Exception exception = new IOException("ehhhhh");
DummySkyFunctionException dummyException =
new DummySkyFunctionException(exception, isDirectlyTransient, /* isCatastrophic= */ false);
ErrorInfo errorInfo =
ErrorInfo.fromException(
new ReifiedSkyFunctionException(dummyException), isTransitivelyTransient);
assertThat(errorInfo.getException()).isSameInstanceAs(exception);
assertThat(errorInfo.getCycleInfo()).isEmpty();
assertThat(errorInfo.isDirectlyTransient()).isEqualTo(isDirectlyTransient);
assertThat(errorInfo.isTransitivelyTransient())
.isEqualTo(isDirectlyTransient || isTransitivelyTransient);
assertThat(errorInfo.isCatastrophic()).isFalse();
}
@Test
public void testFromException_NonTransient() {
runTestFromException(/* isDirectlyTransient= */ false, /* isTransitivelyTransient= */ false);
}
@Test
public void testFromException_DirectlyTransient() {
runTestFromException(/* isDirectlyTransient= */ true, /* isTransitivelyTransient= */ false);
}
@Test
public void testFromException_TransitivelyTransient() {
runTestFromException(/* isDirectlyTransient= */ false, /* isTransitivelyTransient= */ true);
}
@Test
public void testFromException_DirectlyAndTransitivelyTransient() {
runTestFromException(/* isDirectlyTransient= */ true, /* isTransitivelyTransient= */ true);
}
@Test
public void testFromCycle() {
CycleInfo cycle =
CycleInfo.createCycleInfo(
ImmutableList.of(GraphTester.skyKey("PATH, 1234")),
ImmutableList.of(GraphTester.skyKey("CYCLE, 4321")));
ErrorInfo errorInfo = ErrorInfo.fromCycle(cycle);
assertThat(errorInfo.getException()).isNull();
assertThat(errorInfo.isTransitivelyTransient()).isFalse();
assertThat(errorInfo.isCatastrophic()).isFalse();
}
@Test
public void testFromChildErrors() {
CycleInfo cycle =
CycleInfo.createCycleInfo(
ImmutableList.of(GraphTester.skyKey("PATH, 1234")),
ImmutableList.of(GraphTester.skyKey("CYCLE, 4321")));
ErrorInfo cycleErrorInfo = ErrorInfo.fromCycle(cycle);
Exception exception1 = new IOException("ehhhhh");
DummySkyFunctionException dummyException1 =
new DummySkyFunctionException(
exception1, /* isTransient= */ true, /* isCatastrophic= */ false);
ErrorInfo exceptionErrorInfo1 =
ErrorInfo.fromException(
new ReifiedSkyFunctionException(dummyException1), /* isTransitivelyTransient= */ false);
// N.B this ErrorInfo will be catastrophic.
Exception exception2 = new IOException("blahhhhh");
DummySkyFunctionException dummyException2 =
new DummySkyFunctionException(
exception2, /* isTransient= */ false, /* isCatastrophic= */ true);
ErrorInfo exceptionErrorInfo2 =
ErrorInfo.fromException(
new ReifiedSkyFunctionException(dummyException2), /* isTransitivelyTransient= */ false);
SkyKey currentKey = GraphTester.skyKey("CURRENT, 9876");
ErrorInfo errorInfo =
ErrorInfo.fromChildErrors(
currentKey, ImmutableList.of(cycleErrorInfo, exceptionErrorInfo1, exceptionErrorInfo2));
// For simplicity we test the current implementation detail that we choose the first non-null
// exception that we encounter. This isn't necessarily a requirement of the interface, but it
// makes the test convenient and is a way to document the current behavior.
assertThat(errorInfo.getException()).isSameInstanceAs(exception1);
assertThat(errorInfo.getCycleInfo())
.containsExactly(
CycleInfo.createCycleInfo(
ImmutableList.of(currentKey, Iterables.getOnlyElement(cycle.getPathToCycle())),
cycle.getCycle()));
assertThat(errorInfo.isTransitivelyTransient()).isTrue();
assertThat(errorInfo.isCatastrophic()).isTrue();
}
@Test
public void cannotCreateErrorInfoWithoutExceptionOrCycle() {
Exception e =
assertThrows(
RuntimeException.class,
() ->
new ErrorInfo(
/* exception= */ null, /* cycles= */ ImmutableList.of(), false, false, false));
assertThat(e).hasMessageThat().contains("At least one of exception and cycles must be present");
}
@Test
public void cannotCreateErrorInfoWithDirectTransienceButNotTransitiveTransience() {
Exception e =
assertThrows(
RuntimeException.class,
() ->
new ErrorInfo(
new Exception(),
/* cycles= */ ImmutableList.of(),
/* isDirectlyTransient= */ true,
/* isTransitivelyTransient= */ false,
/* isCatastrophic= */ false));
assertThat(e)
.hasMessageThat()
.contains("Cannot be directly transient but not transitively transient");
}
}