blob: 0058570eba0cb65a0f8af46ec0fdc48731c24270 [file] [log] [blame]
// Copyright 2019 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.skyframe;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.FileStateValue;
import com.google.devtools.build.lib.actions.FileValue;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.analysis.ServerDirectories;
import com.google.devtools.build.lib.analysis.util.AnalysisMock;
import com.google.devtools.build.lib.events.NullEventHandler;
import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
import com.google.devtools.build.lib.skyframe.ExternalFilesHelper.ExternalFileAction;
import com.google.devtools.build.lib.testutil.FoundationTestCase;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.lib.vfs.RootedPathAndCasing;
import com.google.devtools.build.lib.vfs.UnixGlob;
import com.google.devtools.build.skyframe.EvaluationContext;
import com.google.devtools.build.skyframe.EvaluationResult;
import com.google.devtools.build.skyframe.InMemoryMemoizingEvaluator;
import com.google.devtools.build.skyframe.RecordingDifferencer;
import com.google.devtools.build.skyframe.SequencedRecordingDifferencer;
import com.google.devtools.build.skyframe.SequentialBuildDriver;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyFunctionName;
import com.google.devtools.build.skyframe.SkyKey;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for {@link PathCasingLookupFunction}. */
@RunWith(JUnit4.class)
public final class PathCasingLookupFunctionTest extends FoundationTestCase {
private SequentialBuildDriver driver;
private RecordingDifferencer differencer;
@Before
public final void setUp() {
AtomicReference<PathPackageLocator> pkgLocator =
new AtomicReference<>(
new PathPackageLocator(
outputBase,
ImmutableList.of(Root.fromPath(rootDirectory)),
BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY));
BlazeDirectories directories =
new BlazeDirectories(
new ServerDirectories(rootDirectory, outputBase, rootDirectory),
rootDirectory,
null,
AnalysisMock.get().getProductName());
ExternalFilesHelper externalFilesHelper =
ExternalFilesHelper.createForTesting(
pkgLocator,
ExternalFileAction.DEPEND_ON_EXTERNAL_PKG_FOR_EXTERNAL_REPO_PATHS,
directories);
AtomicReference<UnixGlob.FilesystemCalls> syscalls =
new AtomicReference<>(UnixGlob.DEFAULT_SYSCALLS);
Map<SkyFunctionName, SkyFunction> skyFunctions = new HashMap<>();
skyFunctions.put(
FileStateValue.FILE_STATE,
new FileStateFunction(new AtomicReference<>(), syscalls, externalFilesHelper));
skyFunctions.put(FileValue.FILE, new FileFunction(pkgLocator));
skyFunctions.put(SkyFunctions.DIRECTORY_LISTING, new DirectoryListingFunction());
skyFunctions.put(
SkyFunctions.DIRECTORY_LISTING_STATE,
new DirectoryListingStateFunction(externalFilesHelper, syscalls));
skyFunctions.put(SkyFunctions.PATH_CASING_LOOKUP, new PathCasingLookupFunction());
differencer = new SequencedRecordingDifferencer();
driver =
new SequentialBuildDriver(new InMemoryMemoizingEvaluator(skyFunctions, differencer, null));
}
private RootedPath rootedPath(String relative) {
return RootedPath.toRootedPath(Root.fromPath(rootDirectory), PathFragment.create(relative));
}
@Test
public void testSanityCheckFilesystemIsCaseInsensitive() {
Path p1 = rootDirectory.getRelative("Foo/Bar");
Path p2 = rootDirectory.getRelative("FOO/BAR");
Path p3 = rootDirectory.getRelative("control");
assertThat(p1).isNotSameInstanceAs(p2);
assertThat(p1).isNotSameInstanceAs(p3);
assertThat(p2).isNotSameInstanceAs(p3);
assertThat(p1).isEqualTo(p2);
assertThat(p1).isNotEqualTo(p3);
}
@Test
public void testPathCasingLookup() throws Exception {
RootedPath a = rootedPath("Foo/Bar/Baz");
RootedPath b = rootedPath("fOO/baR/BAZ");
createFile(a);
assertThat(a).isEqualTo(b);
assertThat(RootedPathAndCasing.create(a)).isNotEqualTo(RootedPathAndCasing.create(b));
assertThat(expectEvalSuccess(a).isCorrect()).isTrue();
assertThat(expectEvalSuccess(b).isCorrect()).isFalse();
}
@Test
public void testNonExistentPath() throws Exception {
RootedPath file = rootedPath("Foo/Bar/Baz.txt");
createFile(file);
RootedPath missing1 = rootedPath("Foo/Bar/x/y");
RootedPath missing2 = rootedPath("Foo/BAR/x/y");
// Non-existent paths are correct if their existing part is correct.
assertThat(expectEvalSuccess(missing1).isCorrect()).isTrue();
assertThat(expectEvalSuccess(missing2).isCorrect()).isFalse();
// Non-existent paths are illegal if their parent exists but is not a directory.
RootedPath bad = rootedPath("Foo/Bar/Baz.txt/x/y");
Exception e = expectEvalFailure(bad);
assertThat(e).hasMessageThat().contains("its parent exists but is not a directory");
}
@Test
public void testNonExistentPathThatComesIntoExistence() throws Exception {
RootedPath a = rootedPath("Foo/Bar/Baz");
RootedPath b = rootedPath("fOO/baR/BAZ");
assertThat(a).isEqualTo(b);
// Expecting RootedPath.toRootedPath not to intern instances, otherwise 'a' would be the same
// instance as 'b' which would nullify this test.
assertThat(a).isNotSameInstanceAs(b);
assertThat(a.toString()).isNotEqualTo(b.toString());
assertThat(RootedPathAndCasing.create(a)).isNotEqualTo(RootedPathAndCasing.create(b));
// Path does not exist, so both casings are correct!
assertThat(expectEvalSuccess(a).isCorrect()).isTrue();
assertThat(expectEvalSuccess(b).isCorrect()).isTrue();
// Path comes into existence.
createFile(a);
// Now only one casing is correct.
assertThat(expectEvalSuccess(a).isCorrect()).isTrue();
assertThat(expectEvalSuccess(b).isCorrect()).isFalse();
}
@Test
public void testExistingPathThatIsThenDeleted() throws Exception {
RootedPath a = rootedPath("Foo/Bar/Baz");
RootedPath b = rootedPath("Foo/Bar/BAZ");
createFile(a);
// Path exists, so only one casing is correct.
assertThat(expectEvalSuccess(a).isCorrect()).isTrue();
assertThat(expectEvalSuccess(b).isCorrect()).isFalse();
// Path no longer exists, both casings are correct.
deleteFile(a);
assertThat(expectEvalSuccess(a).isCorrect()).isTrue();
assertThat(expectEvalSuccess(b).isCorrect()).isTrue();
}
private void createFile(RootedPath p) throws IOException {
Path path = p.asPath();
if (!path.getParentDirectory().exists()) {
scratch.dir(path.getParentDirectory().getPathString());
}
scratch.file(path.getPathString());
invalidateFileAndParents(p);
}
private void deleteFile(RootedPath p) throws IOException {
Path path = p.asPath();
scratch.deleteFile(path.getPathString());
invalidateFileAndParents(p);
}
private EvaluationResult<PathCasingLookupValue> evaluate(SkyKey key) throws Exception {
EvaluationContext evaluationContext =
EvaluationContext.newBuilder()
.setKeepGoing(false)
.setNumThreads(SkyframeExecutor.DEFAULT_THREAD_COUNT)
.setEventHander(NullEventHandler.INSTANCE)
.build();
return driver.evaluate(ImmutableList.of(key), evaluationContext);
}
private PathCasingLookupValue expectEvalSuccess(RootedPath path) throws Exception {
SkyKey key = PathCasingLookupValue.key(path);
EvaluationResult<PathCasingLookupValue> result = evaluate(key);
assertThat(result.hasError()).isFalse();
return result.get(key);
}
private Exception expectEvalFailure(RootedPath path) throws Exception {
SkyKey key = PathCasingLookupValue.key(path);
EvaluationResult<PathCasingLookupValue> result = evaluate(key);
assertThat(result.hasError()).isTrue();
return result.getError().getException();
}
private void invalidateFile(RootedPath path) {
differencer.invalidate(ImmutableList.of(FileStateValue.key(path)));
}
private void invalidateDirectory(RootedPath path) {
invalidateFile(path);
differencer.invalidate(ImmutableList.of(DirectoryListingStateValue.key(path)));
}
private void invalidateFileAndParents(RootedPath p) {
invalidateFile(p);
do {
p = p.getParentDirectory();
invalidateDirectory(p);
} while (!p.getRootRelativePath().isEmpty());
}
}