blob: 0058570eba0cb65a0f8af46ec0fdc48731c24270 [file] [log] [blame]
Laszlo Csomor7d680292019-12-05 03:11:25 -08001// Copyright 2019 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.lib.skyframe;
15
16import static com.google.common.truth.Truth.assertThat;
17
18import com.google.common.collect.ImmutableList;
19import com.google.devtools.build.lib.actions.FileStateValue;
20import com.google.devtools.build.lib.actions.FileValue;
21import com.google.devtools.build.lib.analysis.BlazeDirectories;
22import com.google.devtools.build.lib.analysis.ServerDirectories;
23import com.google.devtools.build.lib.analysis.util.AnalysisMock;
24import com.google.devtools.build.lib.events.NullEventHandler;
25import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
26import com.google.devtools.build.lib.skyframe.ExternalFilesHelper.ExternalFileAction;
27import com.google.devtools.build.lib.testutil.FoundationTestCase;
28import com.google.devtools.build.lib.vfs.Path;
29import com.google.devtools.build.lib.vfs.PathFragment;
30import com.google.devtools.build.lib.vfs.Root;
31import com.google.devtools.build.lib.vfs.RootedPath;
32import com.google.devtools.build.lib.vfs.RootedPathAndCasing;
33import com.google.devtools.build.lib.vfs.UnixGlob;
34import com.google.devtools.build.skyframe.EvaluationContext;
35import com.google.devtools.build.skyframe.EvaluationResult;
36import com.google.devtools.build.skyframe.InMemoryMemoizingEvaluator;
37import com.google.devtools.build.skyframe.RecordingDifferencer;
38import com.google.devtools.build.skyframe.SequencedRecordingDifferencer;
39import com.google.devtools.build.skyframe.SequentialBuildDriver;
40import com.google.devtools.build.skyframe.SkyFunction;
41import com.google.devtools.build.skyframe.SkyFunctionName;
42import com.google.devtools.build.skyframe.SkyKey;
43import java.io.IOException;
44import java.util.HashMap;
45import java.util.Map;
46import java.util.concurrent.atomic.AtomicReference;
47import org.junit.Before;
48import org.junit.Test;
49import org.junit.runner.RunWith;
50import org.junit.runners.JUnit4;
51
52/** Tests for {@link PathCasingLookupFunction}. */
53@RunWith(JUnit4.class)
54public final class PathCasingLookupFunctionTest extends FoundationTestCase {
55
56 private SequentialBuildDriver driver;
57 private RecordingDifferencer differencer;
58
59 @Before
60 public final void setUp() {
61 AtomicReference<PathPackageLocator> pkgLocator =
62 new AtomicReference<>(
63 new PathPackageLocator(
64 outputBase,
65 ImmutableList.of(Root.fromPath(rootDirectory)),
66 BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY));
67 BlazeDirectories directories =
68 new BlazeDirectories(
69 new ServerDirectories(rootDirectory, outputBase, rootDirectory),
70 rootDirectory,
71 null,
72 AnalysisMock.get().getProductName());
73 ExternalFilesHelper externalFilesHelper =
74 ExternalFilesHelper.createForTesting(
75 pkgLocator,
76 ExternalFileAction.DEPEND_ON_EXTERNAL_PKG_FOR_EXTERNAL_REPO_PATHS,
77 directories);
78
79 AtomicReference<UnixGlob.FilesystemCalls> syscalls =
80 new AtomicReference<>(UnixGlob.DEFAULT_SYSCALLS);
81 Map<SkyFunctionName, SkyFunction> skyFunctions = new HashMap<>();
82 skyFunctions.put(
83 FileStateValue.FILE_STATE,
84 new FileStateFunction(new AtomicReference<>(), syscalls, externalFilesHelper));
85 skyFunctions.put(FileValue.FILE, new FileFunction(pkgLocator));
86 skyFunctions.put(SkyFunctions.DIRECTORY_LISTING, new DirectoryListingFunction());
87 skyFunctions.put(
88 SkyFunctions.DIRECTORY_LISTING_STATE,
89 new DirectoryListingStateFunction(externalFilesHelper, syscalls));
90 skyFunctions.put(SkyFunctions.PATH_CASING_LOOKUP, new PathCasingLookupFunction());
91
92 differencer = new SequencedRecordingDifferencer();
93 driver =
94 new SequentialBuildDriver(new InMemoryMemoizingEvaluator(skyFunctions, differencer, null));
95 }
96
97 private RootedPath rootedPath(String relative) {
98 return RootedPath.toRootedPath(Root.fromPath(rootDirectory), PathFragment.create(relative));
99 }
100
101 @Test
102 public void testSanityCheckFilesystemIsCaseInsensitive() {
103 Path p1 = rootDirectory.getRelative("Foo/Bar");
104 Path p2 = rootDirectory.getRelative("FOO/BAR");
105 Path p3 = rootDirectory.getRelative("control");
106 assertThat(p1).isNotSameInstanceAs(p2);
107 assertThat(p1).isNotSameInstanceAs(p3);
108 assertThat(p2).isNotSameInstanceAs(p3);
109 assertThat(p1).isEqualTo(p2);
110 assertThat(p1).isNotEqualTo(p3);
111 }
112
113 @Test
114 public void testPathCasingLookup() throws Exception {
115 RootedPath a = rootedPath("Foo/Bar/Baz");
116 RootedPath b = rootedPath("fOO/baR/BAZ");
117 createFile(a);
118 assertThat(a).isEqualTo(b);
119 assertThat(RootedPathAndCasing.create(a)).isNotEqualTo(RootedPathAndCasing.create(b));
120 assertThat(expectEvalSuccess(a).isCorrect()).isTrue();
121 assertThat(expectEvalSuccess(b).isCorrect()).isFalse();
122 }
123
124 @Test
125 public void testNonExistentPath() throws Exception {
126 RootedPath file = rootedPath("Foo/Bar/Baz.txt");
127 createFile(file);
128 RootedPath missing1 = rootedPath("Foo/Bar/x/y");
129 RootedPath missing2 = rootedPath("Foo/BAR/x/y");
130 // Non-existent paths are correct if their existing part is correct.
131 assertThat(expectEvalSuccess(missing1).isCorrect()).isTrue();
132 assertThat(expectEvalSuccess(missing2).isCorrect()).isFalse();
133 // Non-existent paths are illegal if their parent exists but is not a directory.
134 RootedPath bad = rootedPath("Foo/Bar/Baz.txt/x/y");
135 Exception e = expectEvalFailure(bad);
136 assertThat(e).hasMessageThat().contains("its parent exists but is not a directory");
137 }
138
139 @Test
140 public void testNonExistentPathThatComesIntoExistence() throws Exception {
141 RootedPath a = rootedPath("Foo/Bar/Baz");
142 RootedPath b = rootedPath("fOO/baR/BAZ");
143 assertThat(a).isEqualTo(b);
144 // Expecting RootedPath.toRootedPath not to intern instances, otherwise 'a' would be the same
145 // instance as 'b' which would nullify this test.
146 assertThat(a).isNotSameInstanceAs(b);
147 assertThat(a.toString()).isNotEqualTo(b.toString());
148 assertThat(RootedPathAndCasing.create(a)).isNotEqualTo(RootedPathAndCasing.create(b));
149 // Path does not exist, so both casings are correct!
150 assertThat(expectEvalSuccess(a).isCorrect()).isTrue();
151 assertThat(expectEvalSuccess(b).isCorrect()).isTrue();
152 // Path comes into existence.
153 createFile(a);
154 // Now only one casing is correct.
155 assertThat(expectEvalSuccess(a).isCorrect()).isTrue();
156 assertThat(expectEvalSuccess(b).isCorrect()).isFalse();
157 }
158
159 @Test
160 public void testExistingPathThatIsThenDeleted() throws Exception {
161 RootedPath a = rootedPath("Foo/Bar/Baz");
162 RootedPath b = rootedPath("Foo/Bar/BAZ");
163 createFile(a);
164 // Path exists, so only one casing is correct.
165 assertThat(expectEvalSuccess(a).isCorrect()).isTrue();
166 assertThat(expectEvalSuccess(b).isCorrect()).isFalse();
167 // Path no longer exists, both casings are correct.
168 deleteFile(a);
169 assertThat(expectEvalSuccess(a).isCorrect()).isTrue();
170 assertThat(expectEvalSuccess(b).isCorrect()).isTrue();
171 }
172
173 private void createFile(RootedPath p) throws IOException {
174 Path path = p.asPath();
175 if (!path.getParentDirectory().exists()) {
176 scratch.dir(path.getParentDirectory().getPathString());
177 }
178 scratch.file(path.getPathString());
179 invalidateFileAndParents(p);
180 }
181
182 private void deleteFile(RootedPath p) throws IOException {
183 Path path = p.asPath();
184 scratch.deleteFile(path.getPathString());
185 invalidateFileAndParents(p);
186 }
187
188 private EvaluationResult<PathCasingLookupValue> evaluate(SkyKey key) throws Exception {
189 EvaluationContext evaluationContext =
190 EvaluationContext.newBuilder()
191 .setKeepGoing(false)
192 .setNumThreads(SkyframeExecutor.DEFAULT_THREAD_COUNT)
193 .setEventHander(NullEventHandler.INSTANCE)
194 .build();
195 return driver.evaluate(ImmutableList.of(key), evaluationContext);
196 }
197
198 private PathCasingLookupValue expectEvalSuccess(RootedPath path) throws Exception {
199 SkyKey key = PathCasingLookupValue.key(path);
200 EvaluationResult<PathCasingLookupValue> result = evaluate(key);
201 assertThat(result.hasError()).isFalse();
202 return result.get(key);
203 }
204
205 private Exception expectEvalFailure(RootedPath path) throws Exception {
206 SkyKey key = PathCasingLookupValue.key(path);
207 EvaluationResult<PathCasingLookupValue> result = evaluate(key);
208 assertThat(result.hasError()).isTrue();
209 return result.getError().getException();
210 }
211
212 private void invalidateFile(RootedPath path) {
213 differencer.invalidate(ImmutableList.of(FileStateValue.key(path)));
214 }
215
216 private void invalidateDirectory(RootedPath path) {
217 invalidateFile(path);
218 differencer.invalidate(ImmutableList.of(DirectoryListingStateValue.key(path)));
219 }
220
221 private void invalidateFileAndParents(RootedPath p) {
222 invalidateFile(p);
223 do {
224 p = p.getParentDirectory();
225 invalidateDirectory(p);
226 } while (!p.getRootRelativePath().isEmpty());
227 }
228}