• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2013 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.jimfs;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 import static com.google.common.truth.Fact.fact;
21 import static com.google.common.truth.Fact.simpleFact;
22 import static java.nio.charset.StandardCharsets.UTF_8;
23 import static java.util.Arrays.asList;
24 
25 import com.google.common.collect.ImmutableList;
26 import com.google.common.io.BaseEncoding;
27 import com.google.common.truth.FailureMetadata;
28 import com.google.common.truth.Subject;
29 import java.io.IOException;
30 import java.nio.charset.Charset;
31 import java.nio.file.DirectoryStream;
32 import java.nio.file.Files;
33 import java.nio.file.LinkOption;
34 import java.nio.file.Path;
35 import java.nio.file.PathMatcher;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.List;
39 import org.checkerframework.checker.nullness.compatqual.NullableDecl;
40 
41 /**
42  * Subject for doing assertions on file system paths.
43  *
44  * @author Colin Decker
45  */
46 public final class PathSubject extends Subject {
47 
48   /** Returns the subject factory for doing assertions on paths. */
paths()49   public static Subject.Factory<PathSubject, Path> paths() {
50     return new PathSubjectFactory();
51   }
52 
53   private static final LinkOption[] FOLLOW_LINKS = new LinkOption[0];
54   private static final LinkOption[] NOFOLLOW_LINKS = {LinkOption.NOFOLLOW_LINKS};
55 
56   private final Path actual;
57   protected LinkOption[] linkOptions = FOLLOW_LINKS;
58   private Charset charset = UTF_8;
59 
PathSubject(FailureMetadata failureMetadata, Path subject)60   private PathSubject(FailureMetadata failureMetadata, Path subject) {
61     super(failureMetadata, subject);
62     this.actual = subject;
63   }
64 
toPath(String path)65   private Path toPath(String path) {
66     return actual.getFileSystem().getPath(path);
67   }
68 
69   /** Returns this, for readability of chained assertions. */
and()70   public PathSubject and() {
71     return this;
72   }
73 
74   /** Do not follow links when looking up the path. */
noFollowLinks()75   public PathSubject noFollowLinks() {
76     this.linkOptions = NOFOLLOW_LINKS;
77     return this;
78   }
79 
80   /**
81    * Set the given charset to be used when reading the file at this path as text. Default charset if
82    * not set is UTF-8.
83    */
withCharset(Charset charset)84   public PathSubject withCharset(Charset charset) {
85     this.charset = checkNotNull(charset);
86     return this;
87   }
88 
89   /** Asserts that the path is absolute (it has a root component). */
isAbsolute()90   public PathSubject isAbsolute() {
91     if (!actual.isAbsolute()) {
92       failWithActual(simpleFact("expected to be absolute"));
93     }
94     return this;
95   }
96 
97   /** Asserts that the path is relative (it has no root component). */
isRelative()98   public PathSubject isRelative() {
99     if (actual.isAbsolute()) {
100       failWithActual(simpleFact("expected to be relative"));
101     }
102     return this;
103   }
104 
105   /** Asserts that the path has the given root component. */
hasRootComponent(@ullableDecl String root)106   public PathSubject hasRootComponent(@NullableDecl String root) {
107     Path rootComponent = actual.getRoot();
108     if (root == null && rootComponent != null) {
109       failWithActual("expected to have root component", root);
110     } else if (root != null && !root.equals(rootComponent.toString())) {
111       failWithActual("expected to have root component", root);
112     }
113     return this;
114   }
115 
116   /** Asserts that the path has no name components. */
hasNoNameComponents()117   public PathSubject hasNoNameComponents() {
118     check("getNameCount()").that(actual.getNameCount()).isEqualTo(0);
119     return this;
120   }
121 
122   /** Asserts that the path has the given name components. */
hasNameComponents(String... names)123   public PathSubject hasNameComponents(String... names) {
124     ImmutableList.Builder<String> builder = ImmutableList.builder();
125     for (Path name : actual) {
126       builder.add(name.toString());
127     }
128 
129     if (!builder.build().equals(ImmutableList.copyOf(names))) {
130       failWithActual("expected components", asList(names));
131     }
132     return this;
133   }
134 
135   /** Asserts that the path matches the given syntax and pattern. */
matches(String syntaxAndPattern)136   public PathSubject matches(String syntaxAndPattern) {
137     PathMatcher matcher = actual.getFileSystem().getPathMatcher(syntaxAndPattern);
138     if (!matcher.matches(actual)) {
139       failWithActual("expected to match ", syntaxAndPattern);
140     }
141     return this;
142   }
143 
144   /** Asserts that the path does not match the given syntax and pattern. */
doesNotMatch(String syntaxAndPattern)145   public PathSubject doesNotMatch(String syntaxAndPattern) {
146     PathMatcher matcher = actual.getFileSystem().getPathMatcher(syntaxAndPattern);
147     if (matcher.matches(actual)) {
148       failWithActual("expected not to match", syntaxAndPattern);
149     }
150     return this;
151   }
152 
153   /** Asserts that the path exists. */
exists()154   public PathSubject exists() {
155     if (!Files.exists(actual, linkOptions)) {
156       failWithActual(simpleFact("expected to exist"));
157     }
158     if (Files.notExists(actual, linkOptions)) {
159       failWithActual(simpleFact("expected to exist"));
160     }
161     return this;
162   }
163 
164   /** Asserts that the path does not exist. */
doesNotExist()165   public PathSubject doesNotExist() {
166     if (!Files.notExists(actual, linkOptions)) {
167       failWithActual(simpleFact("expected not to exist"));
168     }
169     if (Files.exists(actual, linkOptions)) {
170       failWithActual(simpleFact("expected not to exist"));
171     }
172     return this;
173   }
174 
175   /** Asserts that the path is a directory. */
isDirectory()176   public PathSubject isDirectory() {
177     exists(); // check for directoryness should imply check for existence
178 
179     if (!Files.isDirectory(actual, linkOptions)) {
180       failWithActual(simpleFact("expected to be directory"));
181     }
182     return this;
183   }
184 
185   /** Asserts that the path is a regular file. */
isRegularFile()186   public PathSubject isRegularFile() {
187     exists(); // check for regular fileness should imply check for existence
188 
189     if (!Files.isRegularFile(actual, linkOptions)) {
190       failWithActual(simpleFact("expected to be regular file"));
191     }
192     return this;
193   }
194 
195   /** Asserts that the path is a symbolic link. */
isSymbolicLink()196   public PathSubject isSymbolicLink() {
197     exists(); // check for symbolic linkness should imply check for existence
198 
199     if (!Files.isSymbolicLink(actual)) {
200       failWithActual(simpleFact("expected to be symbolic link"));
201     }
202     return this;
203   }
204 
205   /** Asserts that the path, which is a symbolic link, has the given path as a target. */
withTarget(String targetPath)206   public PathSubject withTarget(String targetPath) throws IOException {
207     Path actualTarget = Files.readSymbolicLink(actual);
208     if (!actualTarget.equals(toPath(targetPath))) {
209       failWithoutActual(
210           fact("expected link target", targetPath),
211           fact("but target was", actualTarget),
212           fact("for path", actual));
213     }
214     return this;
215   }
216 
217   /**
218    * Asserts that the file the path points to exists and has the given number of links to it. Fails
219    * on a file system that does not support the "unix" view.
220    */
hasLinkCount(int count)221   public PathSubject hasLinkCount(int count) throws IOException {
222     exists();
223 
224     int linkCount = (int) Files.getAttribute(actual, "unix:nlink", linkOptions);
225     if (linkCount != count) {
226       failWithActual("expected to have link count", count);
227     }
228     return this;
229   }
230 
231   /** Asserts that the path resolves to the same file as the given path. */
isSameFileAs(String path)232   public PathSubject isSameFileAs(String path) throws IOException {
233     return isSameFileAs(toPath(path));
234   }
235 
236   /** Asserts that the path resolves to the same file as the given path. */
isSameFileAs(Path path)237   public PathSubject isSameFileAs(Path path) throws IOException {
238     if (!Files.isSameFile(actual, path)) {
239       failWithActual("expected to be same file as", path);
240     }
241     return this;
242   }
243 
244   /** Asserts that the path does not resolve to the same file as the given path. */
isNotSameFileAs(String path)245   public PathSubject isNotSameFileAs(String path) throws IOException {
246     if (Files.isSameFile(actual, toPath(path))) {
247       failWithActual("expected not to be same file as", path);
248     }
249     return this;
250   }
251 
252   /** Asserts that the directory has no children. */
hasNoChildren()253   public PathSubject hasNoChildren() throws IOException {
254     isDirectory();
255 
256     try (DirectoryStream<Path> stream = Files.newDirectoryStream(actual)) {
257       if (stream.iterator().hasNext()) {
258         failWithActual(simpleFact("expected to have no children"));
259       }
260     }
261     return this;
262   }
263 
264   /** Asserts that the directory has children with the given names, in the given order. */
hasChildren(String... children)265   public PathSubject hasChildren(String... children) throws IOException {
266     isDirectory();
267 
268     List<Path> expectedNames = new ArrayList<>();
269     for (String child : children) {
270       expectedNames.add(actual.getFileSystem().getPath(child));
271     }
272 
273     try (DirectoryStream<Path> stream = Files.newDirectoryStream(actual)) {
274       List<Path> actualNames = new ArrayList<>();
275       for (Path path : stream) {
276         actualNames.add(path.getFileName());
277       }
278 
279       if (!actualNames.equals(expectedNames)) {
280         failWithoutActual(
281             fact("expected to have children", expectedNames),
282             fact("but had children", actualNames),
283             fact("for path", actual));
284       }
285     }
286     return this;
287   }
288 
289   /** Asserts that the file has the given size. */
hasSize(long size)290   public PathSubject hasSize(long size) throws IOException {
291     if (Files.size(actual) != size) {
292       failWithActual("expected to have size", size);
293     }
294     return this;
295   }
296 
297   /** Asserts that the file is a regular file containing no bytes. */
containsNoBytes()298   public PathSubject containsNoBytes() throws IOException {
299     return containsBytes(new byte[0]);
300   }
301 
302   /**
303    * Asserts that the file is a regular file containing exactly the byte values of the given ints.
304    */
containsBytes(int... bytes)305   public PathSubject containsBytes(int... bytes) throws IOException {
306     byte[] realBytes = new byte[bytes.length];
307     for (int i = 0; i < bytes.length; i++) {
308       realBytes[i] = (byte) bytes[i];
309     }
310     return containsBytes(realBytes);
311   }
312 
313   /** Asserts that the file is a regular file containing exactly the given bytes. */
containsBytes(byte[] bytes)314   public PathSubject containsBytes(byte[] bytes) throws IOException {
315     isRegularFile();
316     hasSize(bytes.length);
317 
318     byte[] actual = Files.readAllBytes(this.actual);
319     if (!Arrays.equals(bytes, actual)) {
320       System.out.println(BaseEncoding.base16().encode(actual));
321       System.out.println(BaseEncoding.base16().encode(bytes));
322       failWithActual("expected to contain bytes", BaseEncoding.base16().encode(bytes));
323     }
324     return this;
325   }
326 
327   /**
328    * Asserts that the file is a regular file containing the same bytes as the regular file at the
329    * given path.
330    */
containsSameBytesAs(String path)331   public PathSubject containsSameBytesAs(String path) throws IOException {
332     isRegularFile();
333 
334     byte[] expectedBytes = Files.readAllBytes(toPath(path));
335     if (!Arrays.equals(expectedBytes, Files.readAllBytes(actual))) {
336       failWithActual("expected to contain same bytes as", path);
337     }
338     return this;
339   }
340 
341   /**
342    * Asserts that the file is a regular file containing the given lines of text. By default, the
343    * bytes are decoded as UTF-8; for a different charset, use {@link #withCharset(Charset)}.
344    */
containsLines(String... lines)345   public PathSubject containsLines(String... lines) throws IOException {
346     return containsLines(Arrays.asList(lines));
347   }
348 
349   /**
350    * Asserts that the file is a regular file containing the given lines of text. By default, the
351    * bytes are decoded as UTF-8; for a different charset, use {@link #withCharset(Charset)}.
352    */
containsLines(Iterable<String> lines)353   public PathSubject containsLines(Iterable<String> lines) throws IOException {
354     isRegularFile();
355 
356     List<String> expected = ImmutableList.copyOf(lines);
357     List<String> actual = Files.readAllLines(this.actual, charset);
358     check("lines()").that(actual).isEqualTo(expected);
359     return this;
360   }
361 
362   /** Returns an object for making assertions about the given attribute. */
attribute(final String attribute)363   public Attribute attribute(final String attribute) {
364     return new Attribute() {
365       @Override
366       public Attribute is(Object value) throws IOException {
367         Object actualValue = Files.getAttribute(actual, attribute, linkOptions);
368         check("attribute(%s)", attribute).that(actualValue).isEqualTo(value);
369         return this;
370       }
371 
372       @Override
373       public Attribute isNot(Object value) throws IOException {
374         Object actualValue = Files.getAttribute(actual, attribute, linkOptions);
375         check("attribute(%s)", attribute).that(actualValue).isNotEqualTo(value);
376         return this;
377       }
378 
379       @Override
380       public PathSubject and() {
381         return PathSubject.this;
382       }
383     };
384   }
385 
386   private static class PathSubjectFactory implements Subject.Factory<PathSubject, Path> {
387 
388     @Override
389     public PathSubject createSubject(FailureMetadata failureMetadata, Path that) {
390       return new PathSubject(failureMetadata, that);
391     }
392   }
393 
394   /** Interface for assertions about a file attribute. */
395   public interface Attribute {
396 
397     /** Asserts that the value of this attribute is equal to the given value. */
398     Attribute is(Object value) throws IOException;
399 
400     /** Asserts that the value of this attribute is not equal to the given value. */
401     Attribute isNot(Object value) throws IOException;
402 
403     /** Returns the path subject for further chaining. */
404     PathSubject and();
405   }
406 }
407