blob: 31abcda73ac38809eff786905d4808c9825bcdc7 [file] [log] [blame]
// Copyright 2022 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.exec;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.devtools.build.lib.actions.AbstractAction;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.ActionExecutionException;
import com.google.devtools.build.lib.actions.ArtifactRoot;
import com.google.devtools.build.lib.actions.ArtifactRoot.RootType;
import com.google.devtools.build.lib.actions.EnvironmentalExecException;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
import com.google.devtools.build.lib.actions.util.ActionsTestUtil.NullAction;
import com.google.devtools.build.lib.actions.util.DummyExecutor;
import com.google.devtools.build.lib.analysis.actions.DeterministicWriter;
import com.google.devtools.build.lib.events.NullEventHandler;
import com.google.devtools.build.lib.server.FailureDetails.Execution.Code;
import com.google.devtools.build.lib.testing.vfs.SpiedFileSystem;
import com.google.devtools.build.lib.testutil.Scratch;
import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.devtools.build.lib.util.ExitCode;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import java.io.IOException;
import java.io.OutputStream;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link FileWriteStrategy}. */
@RunWith(TestParameterInjector.class)
public final class FileWriteStrategyTest {
private static final IOException INJECTED_EXCEPTION = new IOException("oh no!");
private final FileWriteStrategy fileWriteStrategy = new FileWriteStrategy();
private final SpiedFileSystem fileSystem = SpiedFileSystem.createInMemorySpy();
private final Scratch scratch = new Scratch(fileSystem);
private Path execRoot;
private ArtifactRoot outputRoot;
@Before
public void createOutputRoot() throws IOException {
execRoot = scratch.dir("/execroot");
outputRoot = ArtifactRoot.asDerivedRoot(execRoot, RootType.Output, "bazel-out");
outputRoot.getRoot().asPath().createDirectory();
}
@Test
public void writeOutputToFile_writesCorrectOutput(
@TestParameter({"", "hello", "hello there"}) String content) throws Exception {
AbstractAction action = createAction("file");
var unused =
fileWriteStrategy.writeOutputToFile(
action,
createActionExecutionContext(),
out -> out.write(content.getBytes(UTF_8)),
/* makeExecutable= */ false,
/* isRemotable= */ false);
assertThat(FileSystemUtils.readContent(action.getPrimaryOutput().getPath(), UTF_8))
.isEqualTo(content);
}
private enum FailureMode implements DeterministicWriter {
OPEN_FAILURE {
@Override
void setupFileSystem(SpiedFileSystem fileSystem, PathFragment outputPath) throws IOException {
when(fileSystem.getOutputStream(outputPath, /* append= */ false, /* internal= */ false))
.thenThrow(INJECTED_EXCEPTION);
}
},
WRITE_FAILURE {
@Override
public void writeOutputFile(OutputStream out) throws IOException {
throw INJECTED_EXCEPTION;
}
},
CLOSE_FAILURE {
@Override
void setupFileSystem(SpiedFileSystem fileSystem, PathFragment outputPath) throws IOException {
OutputStream outputStream = mock(OutputStream.class);
doThrow(INJECTED_EXCEPTION).when(outputStream).close();
when(fileSystem.getOutputStream(outputPath, /* append= */ false, /* internal= */ false))
.thenReturn(outputStream);
}
};
void setupFileSystem(SpiedFileSystem fileSystem, PathFragment outputPath) throws IOException {}
@Override
public void writeOutputFile(OutputStream out) throws IOException {}
}
@Test
public void writeOutputToFile_errorInWriter_returnsFailure(@TestParameter FailureMode failureMode)
throws Exception {
AbstractAction action = createAction("file");
failureMode.setupFileSystem(fileSystem, action.getPrimaryOutput().getPath().asFragment());
ExecException e =
assertThrows(
EnvironmentalExecException.class,
() -> {
fileWriteStrategy.writeOutputToFile(
action,
createActionExecutionContext(),
failureMode,
/* makeExecutable= */ false,
/* isRemotable= */ false);
});
assertThat(e).hasCauseThat().isSameInstanceAs(INJECTED_EXCEPTION);
var detailExitCode = getDetailExitCode(e);
assertThat(detailExitCode.getExitCode()).isEqualTo(ExitCode.LOCAL_ENVIRONMENTAL_ERROR);
assertThat(detailExitCode.getFailureDetail().getExecution().getCode())
.isEqualTo(Code.FILE_WRITE_IO_EXCEPTION);
}
private DetailedExitCode getDetailExitCode(ExecException e) {
return ActionExecutionException.fromExecException(e, new ActionsTestUtil.NullAction())
.getDetailedExitCode();
}
private ActionExecutionContext createActionExecutionContext() {
return ActionsTestUtil.createContext(
new DummyExecutor(fileSystem, execRoot), NullEventHandler.INSTANCE);
}
private AbstractAction createAction(String outputRelativePath) {
return new NullAction(
ActionsTestUtil.createArtifactWithRootRelativePath(
outputRoot, PathFragment.create(outputRelativePath)));
}
}