aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test/java/com/google/devtools/build/lib/windows/PathWindowsTest.java
blob: 1bd3779e66df7dd86248d5b7160eb543a8b65f55 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
// 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.lib.windows;

import static com.google.common.truth.Truth.assertThat;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.devtools.build.lib.clock.BlazeClock;
import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.Path.PathFactory;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
import com.google.devtools.build.lib.windows.WindowsFileSystem.WindowsPath;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** A test for windows aspects of {@link Path}. */
@RunWith(JUnit4.class)
public class PathWindowsTest {

  private static final class MockShortPathResolver implements Function<String, String> {
    public List<String> resolutionQueries = new ArrayList<>();

    // Full path to resolved child mapping.
    public Map<String, String> resolutions = new HashMap<>();

    @Override
    public String apply(String path) {
      path = path.toLowerCase();
      resolutionQueries.add(path);
      return resolutions.get(path);
    }
  }

  private FileSystem filesystem;
  private WindowsPath root;
  private final MockShortPathResolver shortPathResolver = new MockShortPathResolver();

  @Before
  public final void initializeFileSystem() throws Exception {
    filesystem =
        new InMemoryFileSystem(BlazeClock.instance()) {
          @Override
          protected PathFactory getPathFactory() {
            return WindowsFileSystem.getPathFactoryForTesting(shortPathResolver);
          }

          @Override
          public boolean isFilePathCaseSensitive() {
            return false;
          }
        };
    root = (WindowsPath) filesystem.getRootDirectory().getRelative("C:/");
    root.createDirectory();
  }

  private void assertAsFragmentWorks(String expected) {
    assertThat(filesystem.getPath(expected).asFragment()).isEqualTo(PathFragment.create(expected));
  }

  @Test
  public void testWindowsPath() {
    Path p = filesystem.getPath("C:/foo/bar");
    assertThat(p.getPathString()).isEqualTo("C:/foo/bar");
    assertThat(p.toString()).isEqualTo("C:/foo/bar");
  }

  @Test
  public void testAsFragmentWindows() {
    assertAsFragmentWorks("C:/");
    assertAsFragmentWorks("C://");
    assertAsFragmentWorks("C:/first");
    assertAsFragmentWorks("C:/first/x/y");
    assertAsFragmentWorks("C:/first/x/y.foo");
  }

  @Test
  public void testGetRelativeWithFragmentWindows() {
    Path dir = filesystem.getPath("C:/first/x");
    assertThat(dir.getRelative(PathFragment.create("y")).toString()).isEqualTo("C:/first/x/y");
    assertThat(dir.getRelative(PathFragment.create("./x")).toString()).isEqualTo("C:/first/x/x");
    assertThat(dir.getRelative(PathFragment.create("../y")).toString()).isEqualTo("C:/first/y");
    assertThat(dir.getRelative(PathFragment.create("../y")).toString()).isEqualTo("C:/first/y");
    assertThat(dir.getRelative(PathFragment.create("../../../y")).toString()).isEqualTo("C:/y");
  }

  @Test
  public void testGetRelativeWithAbsoluteFragmentWindows() {
    Path x = filesystem.getPath("C:/first/x");
    assertThat(x.getRelative(PathFragment.create("C:/x/y")).toString()).isEqualTo("C:/x/y");
  }

  @Test
  public void testGetRelativeWithAbsoluteStringWorksWindows() {
    Path x = filesystem.getPath("C:/first/x");
    assertThat(x.getRelative("C:/x/y").toString()).isEqualTo("C:/x/y");
  }

  @Test
  public void testParentOfRootIsRootWindows() {
    assertThat(root).isSameAs(root.getRelative(".."));
    assertThat(root.getRelative("dots")).isSameAs(root.getRelative("broken/../../dots"));
  }

  @Test
  public void testAbsoluteUnixPathIsRelativeToWindowsUnixRoot() {
    Path actual = root.getRelative("/foo/bar");
    Path expected = root.getRelative("C:/fake/msys/foo/bar");
    assertThat(actual.getPathString()).isEqualTo(expected.getPathString());
    assertThat(actual).isEqualTo(expected);
  }

  @Test
  public void testAbsoluteUnixPathReferringToDriveIsRecognized() {
    Path actual = root.getRelative("/c/foo");
    Path expected = root.getRelative("C:/foo");
    Path weird = root.getRelative("/c:");
    assertThat(actual.getPathString()).isEqualTo(expected.getPathString());
    assertThat(actual).isEqualTo(expected);
    assertThat(weird).isNotEqualTo(expected);
  }

  @Test
  public void testStartsWithWorksOnWindows() {
    assertStartsWithReturnsOnWindows(true, "C:/first/x", "C:/first/x/y");
    assertStartsWithReturnsOnWindows(true, "c:/first/x", "C:/FIRST/X/Y");
    assertStartsWithReturnsOnWindows(true, "C:/FIRST/X", "c:/first/x/y");
    assertStartsWithReturnsOnWindows(true, "/", "C:/");
    assertStartsWithReturnsOnWindows(false, "C:/", "/");
    assertStartsWithReturnsOnWindows(false, "C:/", "D:/");
    assertStartsWithReturnsOnWindows(false, "C:/", "D:/foo");
  }

  @Test
  public void testGetRelative() {
    Path x = filesystem.getPath("C:\\first\\x");
    Path other = x.getRelative("a\\b\\c");
    assertThat(other.asFragment().getPathString()).isEqualTo("C:/first/x/a/b/c");
  }

  private static void assertStartsWithReturnsOnWindows(
      boolean expected, String ancestor, String descendant) {
    FileSystem windowsFileSystem = new WindowsFileSystem();
    Path parent = windowsFileSystem.getPath(ancestor);
    Path child = windowsFileSystem.getPath(descendant);
    assertThat(child.startsWith(parent)).isEqualTo(expected);
  }

  @Test
  public void testChildRegistrationWithTranslatedPaths() {
    // Ensure the Path to "/usr" (actually "C:/fake/msys/usr") is created, path parents/children
    // properly registered.
    WindowsPath usrPath = (WindowsPath) root.getRelative("/usr");
    root.getRelative("dummy_path");

    // Assert that "usr" is not registered as a child of "/".
    final List<String> children = new ArrayList<>(2);
    root.applyToChildren(
        new Predicate<Path>() {
          @Override
          public boolean apply(Path input) {
            children.add(input.getPathString());
            return true;
          }
        });
    assertThat(children).containsAllOf("C:/fake", "C:/dummy_path");

    // Assert that "usr" is registered as a child of "C:/fake/msys/".
    children.clear();
    ((WindowsPath) root.getRelative("C:/fake/msys"))
        .applyToChildren(
            new Predicate<Path>() {
              @Override
              public boolean apply(Path input) {
                children.add(input.getPathString());
                return true;
              }
            });
    assertThat(children).containsExactly("C:/fake/msys/usr");

    assertThat(usrPath).isEqualTo(root.getRelative("C:/fake/msys/usr"));
  }

  @Test
  public void testResolvesShortenedPaths() {
    shortPathResolver.resolutions.put("d:/progra~1", "program files");
    shortPathResolver.resolutions.put("d:/program files/micros~1", "microsoft something");
    shortPathResolver.resolutions.put(
        "d:/program files/microsoft something/foo/~bar~1", "~bar_hello");

    // Assert normal shortpath resolution.
    Path normal = root.getRelative("d:/progra~1/micros~1/foo/~bar~1/baz");
    // The path string has an upper-case drive letter because that's how path printing works.
    assertThat(normal.getPathString())
        .isEqualTo("D:/program files/microsoft something/foo/~bar_hello/baz");
    // Assert that we only try to resolve the path segments that look like they may be shortened.
    assertThat(shortPathResolver.resolutionQueries)
        .containsExactly(
            "d:/progra~1",
            "d:/program files/micros~1",
            "d:/program files/microsoft something/foo/~bar~1");

    // Assert resolving a path that has a segment which doesn't exist but later will.
    shortPathResolver.resolutionQueries.clear();
    Path notYetExistent = root.getRelative("d:/progra~1/micros~1/foo/will~1.exi/bar");
    // The path string has an upper-case drive letter because that's how path printing works.
    assertThat(notYetExistent.getPathString())
        .isEqualTo("D:/program files/microsoft something/foo/will~1.exi/bar");
    // Assert that we only try to resolve the path segments that look like they may be shortened.
    assertThat(shortPathResolver.resolutionQueries)
        .containsExactly(
            "d:/progra~1",
            "d:/program files/micros~1",
            "d:/program files/microsoft something/foo/will~1.exi");

    // Assert that the paths we failed to resolve don't get cached.
    final List<String> children = new ArrayList<>(2);
    Predicate<Path> collector =
        new Predicate<Path>() {
          @Override
          public boolean apply(Path child) {
            children.add(child.getPathString());
            return true;
          }
        };

    WindowsPath msRoot = (WindowsPath) root.getRelative("d:/progra~1/micros~1");
    assertThat(msRoot.getPathString()).isEqualTo("D:/program files/microsoft something");
    msRoot.applyToChildren(collector);
    // The path string has an upper-case drive letter because that's how path printing works.
    assertThat(children).containsExactly("D:/program files/microsoft something/foo");

    // Assert that the non-resolvable path was not cached.
    children.clear();
    WindowsPath foo = (WindowsPath) msRoot.getRelative("foo");
    foo.applyToChildren(collector);
    assertThat(children).containsExactly("D:/program files/microsoft something/foo/~bar_hello");

    // Pretend that a path we already failed to resolve once came into existence.
    shortPathResolver.resolutions.put(
        "d:/program files/microsoft something/foo/will~1.exi", "will.exist");

    // Assert that this time we can resolve the previously non-existent path.
    shortPathResolver.resolutionQueries.clear();
    Path nowExists = root.getRelative("d:/progra~1/micros~1/foo/will~1.exi/bar");
    // The path string has an upper-case drive letter because that's how path printing works.
    assertThat(nowExists.getPathString())
        .isEqualTo("D:/program files/microsoft something/foo/will.exist/bar");
    // Assert that we only try to resolve the path segments that look like they may be shortened.
    assertThat(shortPathResolver.resolutionQueries)
        .containsExactly(
            "d:/progra~1",
            "d:/program files/micros~1",
            "d:/program files/microsoft something/foo/will~1.exi");

    // Assert that this time we cached the previously non-existent path.
    children.clear();
    foo.applyToChildren(collector);
    // The path strings have upper-case drive letters because that's how path printing works.
    children.clear();
    foo.applyToChildren(collector);
    assertThat(children)
        .containsExactly(
            "D:/program files/microsoft something/foo/~bar_hello",
            "D:/program files/microsoft something/foo/will.exist");
  }

  @Test
  public void testCaseInsensitivePathFragment() {
    // equals
    assertThat(PathFragment.create("c:/FOO/BAR")).isEqualTo(PathFragment.create("c:\\foo\\bar"));
    assertThat(PathFragment.create("c:/FOO/BAR")).isNotEqualTo(PathFragment.create("d:\\foo\\bar"));
    assertThat(PathFragment.create("c:/FOO/BAR")).isNotEqualTo(PathFragment.create("/foo/bar"));
    // equals for the string representation
    assertThat(PathFragment.create("c:/FOO/BAR").toString())
        .isNotEqualTo(PathFragment.create("c:/foo/bar").toString());
    // hashCode
    assertThat(PathFragment.create("c:/FOO/BAR").hashCode())
        .isEqualTo(PathFragment.create("c:\\foo\\bar").hashCode());
    assertThat(PathFragment.create("c:/FOO/BAR").hashCode())
        .isNotEqualTo(PathFragment.create("d:\\foo\\bar").hashCode());
    assertThat(PathFragment.create("c:/FOO/BAR").hashCode())
        .isNotEqualTo(PathFragment.create("/foo/bar").hashCode());
    // compareTo
    assertThat(PathFragment.create("c:/FOO/BAR").compareTo(PathFragment.create("c:\\foo\\bar")))
        .isEqualTo(0);
    assertThat(PathFragment.create("c:/FOO/BAR").compareTo(PathFragment.create("d:\\foo\\bar")))
        .isLessThan(0);
    assertThat(PathFragment.create("c:/FOO/BAR").compareTo(PathFragment.create("/foo/bar")))
        .isGreaterThan(0);
    // startsWith
    assertThat(PathFragment.create("c:/FOO/BAR").startsWith(PathFragment.create("c:\\foo")))
        .isTrue();
    assertThat(PathFragment.create("c:/FOO/BAR").startsWith(PathFragment.create("d:\\foo")))
        .isFalse();
    // endsWith
    assertThat(PathFragment.create("c:/FOO/BAR/BAZ").endsWith(PathFragment.create("bar\\baz")))
        .isTrue();
    assertThat(PathFragment.create("c:/FOO/BAR/BAZ").endsWith(PathFragment.create("/bar/baz")))
        .isFalse();
    assertThat(PathFragment.create("c:/FOO/BAR/BAZ").endsWith(PathFragment.create("d:\\bar\\baz")))
        .isFalse();
    // relativeTo
    assertThat(
            PathFragment.create("c:/FOO/BAR/BAZ/QUX")
                .relativeTo(PathFragment.create("c:\\foo\\bar")))
        .isEqualTo(PathFragment.create("Baz/Qux"));
  }

  @Test
  public void testCaseInsensitiveRootedPath() {
    Path ancestor = filesystem.getPath("C:\\foo\\bar");
    assertThat(ancestor).isInstanceOf(WindowsPath.class);
    Path child = filesystem.getPath("C:\\FOO\\Bar\\baz");
    assertThat(child).isInstanceOf(WindowsPath.class);
    assertThat(child.startsWith(ancestor)).isTrue();
    assertThat(child.relativeTo(ancestor)).isEqualTo(PathFragment.create("baz"));
    RootedPath actual = RootedPath.toRootedPath(ancestor, child);
    assertThat(actual.getRoot()).isEqualTo(ancestor);
    assertThat(actual.getRelativePath()).isEqualTo(PathFragment.create("baz"));
  }

  @Test
  public void testToURI() {
    // See https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/
    Path p = root.getRelative("Temp\\Foo Bar.txt");
    URI uri = p.toURI();
    assertThat(uri.toString()).isEqualTo("file:///C:/Temp/Foo%20Bar.txt");
  }
}