Skip to content

Commit e4c9df6

Browse files
warnerbros-zzjoel-costigliola
authored andcommitted
Add assertIsDirectoryRecursivelyContaining file assertion
Fixes assertj#1740
1 parent 103ddb2 commit e4c9df6

14 files changed

+638
-14
lines changed

src/main/java/org/assertj/core/api/AbstractFileAssert.java

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,94 @@ public SELF isDirectoryContaining(String syntaxAndPattern) {
939939
return myself;
940940
}
941941

942+
/**
943+
* Verify that the actual {@code File} directory or any of its subdirectories (recursively) contains at least one file
944+
* matching the given {@code String} interpreted as a path matcher (as per {@link FileSystem#getPathMatcher(String)}).
945+
* <p>
946+
* That methods performs the same assertion as {@link #isDirectoryContaining(String syntaxAndPattern)} but recursively.
947+
* <p>
948+
* Note that the actual {@link File} must exist and be a directory.
949+
* <p>
950+
* Examples given the following directory structure:
951+
* <pre><code class="text"> root
952+
* |—— foo
953+
* | |—— foobar
954+
* | |—— foo-file-1.ext
955+
* |—— foo-file-2.ext</code>
956+
* </pre>
957+
*
958+
* <pre><code class="java"> File root = new File("root");
959+
*
960+
* // The following assertions succeed:
961+
* assertThat(root).isDirectoryRecursivelyContaining("glob:**foo")
962+
* .isDirectoryRecursivelyContaining("glob:**ooba*")
963+
* .isDirectoryRecursivelyContaining("glob:**file-1.ext")
964+
* .isDirectoryRecursivelyContaining("regex:.*file-2.*")
965+
* .isDirectoryRecursivelyContaining("glob:**.{ext,dummy}");
966+
*
967+
* // The following assertions fail:
968+
* assertThat(root).isDirectoryRecursivelyContaining("glob:**fooba");
969+
* assertThat(root).isDirectoryRecursivelyContaining("glob:**.bin");
970+
* assertThat(root).isDirectoryRecursivelyContaining("glob:**.{java,class}"); </code></pre>
971+
*
972+
* @param syntaxAndPattern the syntax and pattern for {@link java.nio.file.PathMatcher} as described in {@link FileSystem#getPathMatcher(String)}.
973+
* @return {@code this} assertion object.
974+
* @throws NullPointerException if the given syntaxAndPattern is {@code null}.
975+
* @throws AssertionError if actual is {@code null}.
976+
* @throws AssertionError if actual does not exist.
977+
* @throws AssertionError if actual is not a directory.
978+
* @throws AssertionError if actual does not contain recursively any files matching the given path matcher.
979+
* @see FileSystem#getPathMatcher(String)
980+
* @since 3.16.0
981+
*/
982+
public SELF isDirectoryRecursivelyContaining(String syntaxAndPattern) {
983+
files.assertIsDirectoryRecursivelyContaining(info, actual, syntaxAndPattern);
984+
return myself;
985+
}
986+
987+
/**
988+
* Verify that the actual {@code File} directory or any of its subdirectories (recursively) contains at least one file
989+
* matching the given {@code Predicate<File>}.
990+
* <p>
991+
* That methods performs the same assertion as {@link #isDirectoryContaining(Predicate filter)} but recursively.
992+
* <p>
993+
* Note that the actual {@link File} must exist and be a directory.
994+
* <p>
995+
* Examples given the following directory structure:
996+
* <pre><code class="text"> root
997+
* |—— foo
998+
* | |—— foobar
999+
* | |—— foo-file-1.ext
1000+
* |—— foo-file-2.ext</code>
1001+
* </pre>
1002+
*
1003+
* Here are some assertions examples:
1004+
* <pre><code class="java"> File root = new File("root");
1005+
*
1006+
* // The following assertions succeed:
1007+
* assertThat(root).isDirectoryRecursivelyContaining(file -&gt; file.getName().startsWith("foo-file-1"))
1008+
* .isDirectoryRecursivelyContaining(file -&gt; file.getName().endsWith("file-2.ext"))
1009+
* .isDirectoryRecursivelyContaining(file -&gt; file.getName().equals("foo"))
1010+
* .isDirectoryRecursivelyContaining(file -&gt; file.getParentFile().getName().equals("foo"))
1011+
*
1012+
* // The following assertions fail:
1013+
* assertThat(root).isDirectoryRecursivelyContaining(file -&gt; file.getName().equals("foo-file-1"))
1014+
* assertThat(root).isDirectoryRecursivelyContaining(file -&gt; file.getName().equals("foo/foobar")); </code></pre>
1015+
*
1016+
* @param filter the filter for files located inside {@code actual}'s directory.
1017+
* @return {@code this} assertion object.
1018+
* @throws NullPointerException if the given filter is {@code null}.
1019+
* @throws AssertionError if actual is {@code null}.
1020+
* @throws AssertionError if actual does not exist.
1021+
* @throws AssertionError if actual is not a directory.
1022+
* @throws AssertionError if actual does not contain recursively any files matching the given predicate.
1023+
* @since 3.16.0
1024+
*/
1025+
public SELF isDirectoryRecursivelyContaining(Predicate<File> filter) {
1026+
files.assertIsDirectoryRecursivelyContaining(info, actual, filter);
1027+
return myself;
1028+
}
1029+
9421030
/**
9431031
* Verify that the actual {@code File} is a directory that does not contain any files matching the given {@code Predicate<File>}.
9441032
* <p>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
3+
* the License. You may obtain a copy of the License at
4+
*
5+
* http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
8+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
9+
* specific language governing permissions and limitations under the License.
10+
*
11+
* Copyright 2012-2020 the original author or authors.
12+
*/
13+
package org.assertj.core.error;
14+
15+
import static org.assertj.core.util.Strings.escapePercent;
16+
17+
import java.io.File;
18+
import java.util.List;
19+
20+
/**
21+
* Creates an error message indicating that an assertion that verifies a group of elements contains recursively a given set of values failed.
22+
*
23+
* @author David Haccoun
24+
* @author Joel Costigliola
25+
*/
26+
public class ShouldContainRecursively extends BasicErrorMessageFactory {
27+
28+
public static ErrorMessageFactory directoryShouldContainRecursively(File actual, List<String> directoryContent,
29+
String filterDescription) {
30+
return new ShouldContainRecursively(actual, directoryContent, filterDescription);
31+
}
32+
33+
private ShouldContainRecursively(Object actual, List<String> directoryContent, String filterDescription) {
34+
// not passing directoryContent and filterDescription as parameter to avoid AssertJ default String formatting
35+
super("%nExpecting directory or any of its subdirectories(recursively):%n" +
36+
" <%s>%n" +
37+
"to contain at least one file matching " + escapePercent(filterDescription) + " but there was none.%n" +
38+
"The directory content was:%n " + escapePercent(directoryContent.toString()),
39+
actual);
40+
}
41+
42+
}

src/main/java/org/assertj/core/internal/Files.java

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import static java.lang.String.format;
1616
import static java.nio.file.Files.readAllBytes;
17+
import static java.util.Comparator.comparing;
1718
import static java.util.Objects.requireNonNull;
1819
import static java.util.stream.Collectors.toList;
1920
import static org.assertj.core.error.ShouldBeAbsolutePath.shouldBeAbsolutePath;
@@ -25,6 +26,7 @@
2526
import static org.assertj.core.error.ShouldBeRelativePath.shouldBeRelativePath;
2627
import static org.assertj.core.error.ShouldBeWritable.shouldBeWritable;
2728
import static org.assertj.core.error.ShouldContain.directoryShouldContain;
29+
import static org.assertj.core.error.ShouldContainRecursively.directoryShouldContainRecursively;
2830
import static org.assertj.core.error.ShouldExist.shouldExist;
2931
import static org.assertj.core.error.ShouldHaveBinaryContent.shouldHaveBinaryContent;
3032
import static org.assertj.core.error.ShouldHaveContent.shouldHaveContent;
@@ -48,11 +50,13 @@
4850
import java.io.UncheckedIOException;
4951
import java.nio.charset.Charset;
5052
import java.nio.charset.MalformedInputException;
53+
import java.nio.file.Path;
5154
import java.nio.file.PathMatcher;
5255
import java.security.MessageDigest;
5356
import java.security.NoSuchAlgorithmException;
5457
import java.util.List;
5558
import java.util.function.Predicate;
59+
import java.util.stream.Stream;
5660

5761
import org.assertj.core.api.AssertionInfo;
5862
import org.assertj.core.util.VisibleForTesting;
@@ -475,9 +479,21 @@ public void assertIsDirectoryContaining(AssertionInfo info, File actual, Predica
475479
}
476480

477481
public void assertIsDirectoryContaining(AssertionInfo info, File actual, String syntaxAndPattern) {
478-
requireNonNull(syntaxAndPattern, "The syntax and pattern to build PathMatcher should not be null");
479-
Predicate<File> pathMatcher = pathMatcher(info, actual, syntaxAndPattern);
480-
assertIsDirectoryContaining(info, actual, pathMatcher, format("the '%s' pattern", syntaxAndPattern));
482+
requireNonNull(syntaxAndPattern, "The syntax and pattern should not be null");
483+
Predicate<File> fileMatcher = fileMatcher(info, actual, syntaxAndPattern);
484+
assertIsDirectoryContaining(info, actual, fileMatcher, format("the '%s' pattern", syntaxAndPattern));
485+
}
486+
487+
public void assertIsDirectoryRecursivelyContaining(AssertionInfo info, File actual, String syntaxAndPattern) {
488+
requireNonNull(syntaxAndPattern, "The syntax and pattern should not be null");
489+
Predicate<File> fileMatcher = fileMatcher(info, actual, syntaxAndPattern);
490+
assertIsDirectoryRecursivelyContaining(info, actual, fileMatcher,
491+
format("the '%s' pattern", syntaxAndPattern));
492+
}
493+
494+
public void assertIsDirectoryRecursivelyContaining(AssertionInfo info, File actual, Predicate<File> filter) {
495+
requireNonNull(filter, "The files filter should not be null");
496+
assertIsDirectoryRecursivelyContaining(info, actual, filter, "the given filter");
481497
}
482498

483499
public void assertIsDirectoryNotContaining(AssertionInfo info, File actual, Predicate<File> filter) {
@@ -486,9 +502,9 @@ public void assertIsDirectoryNotContaining(AssertionInfo info, File actual, Pred
486502
}
487503

488504
public void assertIsDirectoryNotContaining(AssertionInfo info, File actual, String syntaxAndPattern) {
489-
requireNonNull(syntaxAndPattern, "The syntax and pattern to build PathMatcher should not be null");
490-
Predicate<File> pathMatcher = pathMatcher(info, actual, syntaxAndPattern);
491-
assertIsDirectoryNotContaining(info, actual, pathMatcher, format("the '%s' pattern", syntaxAndPattern));
505+
requireNonNull(syntaxAndPattern, "The syntax and pattern should not be null");
506+
Predicate<File> fileMatcher = fileMatcher(info, actual, syntaxAndPattern);
507+
assertIsDirectoryNotContaining(info, actual, fileMatcher, format("the '%s' pattern", syntaxAndPattern));
492508
}
493509

494510
public static List<String> toFileNames(List<File> files) {
@@ -497,6 +513,12 @@ public static List<String> toFileNames(List<File> files) {
497513
.collect(toList());
498514
}
499515

516+
public static List<String> toAbsolutePaths(List<File> files) {
517+
return files.stream()
518+
.map(File::getAbsolutePath)
519+
.collect(toList());
520+
}
521+
500522
// non public section
501523

502524
private List<File> filterDirectory(AssertionInfo info, File actual, Predicate<File> filter) {
@@ -529,7 +551,46 @@ private List<String> directoryContentDescription(AssertionInfo info, File actual
529551
return toFileNames(directoryContent(info, actual));
530552
}
531553

532-
private Predicate<File> pathMatcher(AssertionInfo info, File actual, String syntaxAndPattern) {
554+
// BEGIN - recursively assertion private methods
555+
private boolean isDirectoryRecursivelyContaining(AssertionInfo info, File actual, Predicate<File> filter) {
556+
assertIsDirectory(info, actual);
557+
try (Stream<File> fileStream = createRecursiveStreamOfFile(actual)) {
558+
return fileStream.anyMatch(filter);
559+
}
560+
}
561+
562+
private List<File> directoryRecursiveContent(File actual) {
563+
try (Stream<File> fileStream = createRecursiveStreamOfFile(actual)) {
564+
return fileStream.sorted(comparing(File::getAbsolutePath))
565+
.collect(toList());
566+
}
567+
}
568+
569+
private Stream<File> createRecursiveStreamOfFile(File directory) {
570+
Path path = directory.toPath();
571+
try {
572+
return java.nio.file.Files.walk(path)
573+
.filter(p -> !p.equals(path))
574+
.map(Path::toFile);
575+
} catch (IOException e) {
576+
throw new UncheckedIOException(format("Unable to walk recursively the directory :<%s>", path), e);
577+
}
578+
}
579+
580+
private void assertIsDirectoryRecursivelyContaining(AssertionInfo info, File actual, Predicate<File> filter,
581+
String filterPresentation) {
582+
if (!isDirectoryRecursivelyContaining(info, actual, filter)) {
583+
throw failures.failure(info, directoryShouldContainRecursively(actual, directoryRecursiveContentDescription(actual),
584+
filterPresentation));
585+
}
586+
}
587+
588+
private List<String> directoryRecursiveContentDescription(File actual) {
589+
return toAbsolutePaths(directoryRecursiveContent(actual));
590+
}
591+
// END - recursively assertion private methods
592+
593+
private static Predicate<File> fileMatcher(AssertionInfo info, File actual, String syntaxAndPattern) {
533594
assertNotNull(info, actual);
534595
PathMatcher pathMatcher = actual.toPath().getFileSystem().getPathMatcher(syntaxAndPattern);
535596
return file -> pathMatcher.matches(file.toPath());

src/main/java/org/assertj/core/internal/Paths.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ public void assertIsDirectoryContaining(AssertionInfo info, Path actual, Predica
366366
}
367367

368368
public void assertIsDirectoryContaining(AssertionInfo info, Path actual, String syntaxAndPattern) {
369-
requireNonNull(syntaxAndPattern, "The syntax and pattern to build PathMatcher should not be null");
369+
requireNonNull(syntaxAndPattern, "The syntax and pattern should not be null");
370370
PathMatcher pathMatcher = pathMatcher(info, actual, syntaxAndPattern);
371371
assertIsDirectoryContaining(info, actual, pathMatcher::matches, format("the '%s' pattern", syntaxAndPattern));
372372
}
@@ -377,7 +377,7 @@ public void assertIsDirectoryNotContaining(AssertionInfo info, Path actual, Pred
377377
}
378378

379379
public void assertIsDirectoryNotContaining(AssertionInfo info, Path actual, String syntaxAndPattern) {
380-
requireNonNull(syntaxAndPattern, "The syntax and pattern to build PathMatcher should not be null");
380+
requireNonNull(syntaxAndPattern, "The syntax and pattern should not be null");
381381
PathMatcher pathMatcher = pathMatcher(info, actual, syntaxAndPattern);
382382
assertIsDirectoryNotContaining(info, actual, pathMatcher::matches, format("the '%s' pattern", syntaxAndPattern));
383383
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
3+
* the License. You may obtain a copy of the License at
4+
*
5+
* http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
8+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
9+
* specific language governing permissions and limitations under the License.
10+
*
11+
* Copyright 2012-2020 the original author or authors.
12+
*/
13+
package org.assertj.core.api.file;
14+
15+
import static org.mockito.Mockito.verify;
16+
17+
import java.io.File;
18+
import java.util.function.Predicate;
19+
20+
import org.assertj.core.api.FileAssert;
21+
import org.assertj.core.api.FileAssertBaseTest;
22+
23+
/**
24+
* Tests for <code>{@link FileAssert#isDirectoryRecursivelyContaining(Predicate)}</code>
25+
*
26+
* @author David Haccoun
27+
*/
28+
public class FileAssert_isDirectoryRecursivelyContaining_Predicate_Test extends FileAssertBaseTest {
29+
30+
private final Predicate<File> anyFilter = path -> true;
31+
32+
@Override
33+
protected FileAssert invoke_api_method() {
34+
return assertions.isDirectoryRecursivelyContaining(anyFilter);
35+
}
36+
37+
@Override
38+
protected void verify_internal_effects() {
39+
verify(files).assertIsDirectoryRecursivelyContaining(getInfo(assertions), getActual(assertions), anyFilter);
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
3+
* the License. You may obtain a copy of the License at
4+
*
5+
* http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
8+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
9+
* specific language governing permissions and limitations under the License.
10+
*
11+
* Copyright 2012-2020 the original author or authors.
12+
*/
13+
package org.assertj.core.api.file;
14+
15+
import static org.mockito.Mockito.verify;
16+
17+
import org.assertj.core.api.FileAssert;
18+
import org.assertj.core.api.FileAssertBaseTest;
19+
20+
/**
21+
* Tests for <code>{@link FileAssert#isDirectoryRecursivelyContaining(String)}</code>
22+
*
23+
* @author David Haccoun
24+
*/
25+
public class FileAssert_isDirectoryRecursivelyContaining_SyntaxAndPattern_Test extends FileAssertBaseTest {
26+
27+
private final String syntaxAndPattern = "glob:*.java";
28+
29+
@Override
30+
protected FileAssert invoke_api_method() {
31+
return assertions.isDirectoryRecursivelyContaining(syntaxAndPattern);
32+
}
33+
34+
@Override
35+
protected void verify_internal_effects() {
36+
verify(files).assertIsDirectoryRecursivelyContaining(getInfo(assertions), getActual(assertions), syntaxAndPattern);
37+
}
38+
}

src/test/java/org/assertj/core/internal/FilesBaseTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.io.FileFilter;
2626
import java.io.IOException;
2727
import java.io.InputStream;
28+
import java.io.UncheckedIOException;
2829
import java.nio.file.FileSystem;
2930
import java.nio.file.Path;
3031
import java.util.LinkedHashMap;
@@ -106,7 +107,7 @@ protected File mockRegularFile(String... names) {
106107
try {
107108
given(nioFilesWrapper.newInputStream(path.toPath())).willReturn(new ByteArrayInputStream(new byte[0]));
108109
} catch (IOException e) {
109-
assertThat(e).describedAs("Should not happen").isNull();
110+
throw new UncheckedIOException("error during nioFilesWrapper mock recording", e);
110111
}
111112
return path;
112113
}

0 commit comments

Comments
 (0)