Skip to content

Commit 432688e

Browse files
Added an extended ResourceReader that can retrieve test resource files that aren't tightly coupled to the default file naming schemes
1 parent 048495b commit 432688e

File tree

9 files changed

+227
-0
lines changed

9 files changed

+227
-0
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package emissary.util.io;
2+
3+
import org.apache.commons.io.FilenameUtils;
4+
import org.apache.commons.lang3.StringUtils;
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
8+
import java.io.File;
9+
import java.io.IOException;
10+
import java.net.URL;
11+
import java.nio.file.Files;
12+
import java.nio.file.Path;
13+
import java.util.ArrayList;
14+
import java.util.Collections;
15+
import java.util.List;
16+
import java.util.function.Predicate;
17+
import java.util.stream.Collectors;
18+
import java.util.stream.Stream;
19+
20+
import static org.apache.commons.lang3.StringUtils.isNotBlank;
21+
import static org.apache.commons.lang3.StringUtils.substringBeforeLast;
22+
23+
/**
24+
* A {@link ResourceReader} extended to find data files based purely on their location, ignoring the default naming
25+
* convention used by the {@link ResourceReader#findDataResourcesFor(Class)} method.
26+
* <p>
27+
* This class is primarily used to find payload files for Identification tests that can benefit from more
28+
* content-representative file names.
29+
* </p>
30+
*/
31+
public class GreedyResourceReader extends ResourceReader {
32+
private static final Logger logger = LoggerFactory.getLogger(GreedyResourceReader.class);
33+
34+
public static final String PAYLOADS_FOLDER = "payloads";
35+
public static final String ANSWERS_FOLDER = "answers";
36+
37+
public static final Predicate<String> IS_XML_FILE = filename -> "xml".equals(FilenameUtils.getExtension(filename));
38+
39+
/**
40+
* Returns the project-relative paths of test files for the specified test class. The files should be underneath a
41+
* "payloads" subdirectory of the test class directory. Additional subdirectories can exist within the payloads
42+
* directory itself, and any files found within will be included in the results.
43+
*
44+
* @param c test class for which to perform the search
45+
* @return list of project-relative test file paths
46+
*/
47+
public List<String> findAllPayloadFilesFor(Class<?> c) {
48+
URL url = this.which(c);
49+
if (url == null || !url.getProtocol().equals("file")) {
50+
return Collections.emptyList();
51+
}
52+
53+
List<String> results = new ArrayList<>(findAllFilesUnderClassNameSubDir(c, url, PAYLOADS_FOLDER));
54+
Collections.sort(results);
55+
return results;
56+
}
57+
58+
/**
59+
* Returns the project-relative paths of test files for the specified test class. The files should be underneath a
60+
* "payloads" subdirectory of the test class directory. Additional subdirectories can exist within the payloads
61+
* directory itself, and any files found within will be included in the results.
62+
*
63+
* @param c test class for which to perform the search
64+
* @return list of project-relative test file paths
65+
*/
66+
public List<String> findAllAnswerFilesFor(Class<?> c) {
67+
URL url = this.which(c);
68+
if (url == null || !url.getProtocol().equals("file")) {
69+
return Collections.emptyList();
70+
}
71+
72+
List<String> results = findAllFilesUnderClassNameSubDir(c, url, ANSWERS_FOLDER, IS_XML_FILE);
73+
Collections.sort(results);
74+
return results;
75+
}
76+
77+
/**
78+
* Finds all files beneath the specified subdirectory of the test class resource folder
79+
*
80+
* @param c test class for which the resource files exist
81+
* @param url location from which the classLoader looded the test class
82+
* @param subDirName subdirectory that contains the files
83+
* @return List of test resource file paths
84+
*/
85+
private List<String> findAllFilesUnderClassNameSubDir(Class<?> c, URL url, final String subDirName) {
86+
return findAllFilesUnderClassNameSubDir(c, url, subDirName, StringUtils::isNotBlank);
87+
}
88+
89+
/**
90+
* Finds the files beneath a given test class resource folder, filtered by a provided {@link Predicate<String>}
91+
*
92+
* @param c test class for which the resource files exist
93+
* @param url location from which the classLoader loaded the test class
94+
* @param subDirName subdirectory that contains the files
95+
* @param fileFilter Predicate used to filter the list of discovered files
96+
* @return List of test resource file paths
97+
*/
98+
private List<String> findAllFilesUnderClassNameSubDir(Class<?> c, URL url, final String subDirName, final Predicate<String> fileFilter) {
99+
String classNameInPathFormat = getResourceName(c);
100+
Path subDir = Path.of(getFullPathOfTestClassResourceFolder(url, c), subDirName);
101+
File testClassDir = subDir.toFile();
102+
if (testClassDir.exists() && testClassDir.isDirectory()) {
103+
try (Stream<Path> theList = Files.walk(testClassDir.toPath())) {
104+
return theList.filter(Files::isRegularFile)
105+
.map(testClassDir.toPath()::relativize)
106+
.map(filePath -> classNameInPathFormat + "/" + subDirName + "/" + filePath)
107+
.filter(fileFilter::test)
108+
.collect(Collectors.toList());
109+
110+
} catch (IOException e) {
111+
logger.debug("Failed to retrieve files for class {}", c.getName(), e);
112+
}
113+
}
114+
115+
return Collections.emptyList();
116+
}
117+
118+
/**
119+
* Gets the absolute path of a test class runtime resource folder
120+
*
121+
* @param url URL from which the ClassLoader loaded the test class
122+
* @param c test class
123+
* @return test class folder path
124+
*/
125+
protected String getFullPathOfTestClassResourceFolder(URL url, Class<?> c) {
126+
String classNameInPathFormat = getResourceName(c);
127+
if (url.getPath().contains(CLASS_SUFFIX)) {
128+
// return the URL minus the ".class" suffix
129+
return StringUtils.substringBeforeLast(url.getPath(), CLASS_SUFFIX);
130+
}
131+
132+
return StringUtils.join(url.getPath(), "/", classNameInPathFormat);
133+
}
134+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package emissary.util.io;
2+
3+
import emissary.test.core.junit5.UnitTest;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
import java.io.IOException;
8+
import java.io.InputStream;
9+
import java.nio.file.Path;
10+
import java.util.Arrays;
11+
import java.util.List;
12+
13+
import static org.junit.jupiter.api.Assertions.assertEquals;
14+
import static org.junit.jupiter.api.Assertions.assertFalse;
15+
import static org.junit.jupiter.api.Assertions.assertNotNull;
16+
import static org.junit.jupiter.api.Assertions.assertTrue;
17+
import static org.junit.jupiter.api.Assertions.fail;
18+
19+
public class GreedyResourceReaderTest extends UnitTest {
20+
21+
22+
@Test
23+
void testPayloadFileLocation() {
24+
25+
// files in the "payloads" subdirectory should be found as resources
26+
List<String> testFileNames = Arrays.asList("payloads/File1.txt", "payloads/subdir/sample.md");
27+
// Non-payload.txt is in the test class directory, but not beneath its "payloads" subdirectory
28+
final String NON_PAYLOAD = "Non-payload.txt";
29+
30+
GreedyResourceReader grr = new GreedyResourceReader();
31+
String testClassDir = grr.getResourceName(this.getClass());
32+
33+
List<String> resources = grr.findAllPayloadFilesFor(this.getClass());
34+
assertNotNull(resources, "Resources must not be null");
35+
assertEquals(testFileNames.size(), resources.size(), "All data resources not found");
36+
37+
testFileNames.stream()
38+
.map(t -> Path.of(testClassDir, t))
39+
.forEach(p -> assertTrue(resources.contains(p.toString())));
40+
41+
assertFalse(resources.contains(Path.of(testClassDir, NON_PAYLOAD).toString()));
42+
43+
for (String rez : resources) {
44+
try (InputStream is = grr.getResourceAsStream(rez)) {
45+
assertNotNull(is, "Failed to open " + rez);
46+
} catch (IOException e) {
47+
fail("Failed to open " + rez, e);
48+
}
49+
}
50+
}
51+
52+
53+
@Test
54+
void testAnswerFileLocation() {
55+
56+
// files in the "payloads" subdirectory should be found as resources
57+
List<String> testAnswerFileNames = Arrays.asList("answers/File1.txt.xml", "answers/subdir/sample.md.xml");
58+
59+
// files that should NOT be detected as "answer" files based on their locations
60+
List<String> misplacedAnswerFileNames = Arrays.asList("Non-answer.xml", "answers/README");
61+
62+
GreedyResourceReader grr = new GreedyResourceReader();
63+
String testClassDir = grr.getResourceName(this.getClass());
64+
65+
List<String> answerFiles = grr.findAllAnswerFilesFor(this.getClass());
66+
assertNotNull(answerFiles, "Resources must not be null");
67+
assertEquals(testAnswerFileNames.size(), answerFiles.size(), "Not all answer files not found");
68+
69+
testAnswerFileNames.stream()
70+
.map(t -> Path.of(testClassDir, t))
71+
.forEach(p -> assertTrue(answerFiles.contains(p.toString())));
72+
73+
misplacedAnswerFileNames.stream()
74+
.map(t -> Path.of(testClassDir, t))
75+
.forEach(p -> assertFalse(answerFiles.contains(p.toString())));
76+
77+
78+
for (String file : answerFiles) {
79+
try (InputStream is = grr.getResourceAsStream(file)) {
80+
assertNotNull(is, "Failed to open " + file);
81+
} catch (IOException e) {
82+
fail("Failed to open " + file, e);
83+
}
84+
}
85+
}
86+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<root>file is in wrong directory</root>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ignore me
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<root>ignore the file content</root>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
not an answer file
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<root>ignore the file content</root>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Ignore the content
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
## this is a sample file

0 commit comments

Comments
 (0)