-
Couldn't load subscription status.
- Fork 127
Added a ResourceReader that can retrieve test resource files in nested locations #556
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
drivenflywheel
wants to merge
1
commit into
NationalSecurityAgency:main
Choose a base branch
from
drivenflywheel:GreedyResourceReader
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
134 changes: 134 additions & 0 deletions
134
src/main/java/emissary/util/io/GreedyResourceReader.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| package emissary.util.io; | ||
|
|
||
| import org.apache.commons.io.FilenameUtils; | ||
| import org.apache.commons.lang3.StringUtils; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.net.URL; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.function.Predicate; | ||
| import java.util.stream.Collectors; | ||
| import java.util.stream.Stream; | ||
|
|
||
| import static org.apache.commons.lang3.StringUtils.isNotBlank; | ||
| import static org.apache.commons.lang3.StringUtils.substringBeforeLast; | ||
|
|
||
| /** | ||
| * A {@link ResourceReader} extended to find data files based purely on their location, ignoring the default naming | ||
| * convention used by the {@link ResourceReader#findDataResourcesFor(Class)} method. | ||
| * <p> | ||
| * This class is primarily used to find payload files for Identification tests that can benefit from more | ||
| * content-representative file names. | ||
| * </p> | ||
| */ | ||
| public class GreedyResourceReader extends ResourceReader { | ||
| private static final Logger logger = LoggerFactory.getLogger(GreedyResourceReader.class); | ||
|
|
||
| public static final String PAYLOADS_FOLDER = "payloads"; | ||
| public static final String ANSWERS_FOLDER = "answers"; | ||
|
|
||
| public static final Predicate<String> IS_XML_FILE = filename -> "xml".equals(FilenameUtils.getExtension(filename)); | ||
|
|
||
| /** | ||
| * Returns the project-relative paths of test files for the specified test class. The files should be underneath a | ||
| * "payloads" subdirectory of the test class directory. Additional subdirectories can exist within the payloads | ||
| * directory itself, and any files found within will be included in the results. | ||
| * | ||
| * @param c test class for which to perform the search | ||
| * @return list of project-relative test file paths | ||
| */ | ||
| public List<String> findAllPayloadFilesFor(Class<?> c) { | ||
| URL url = this.which(c); | ||
| if (url == null || !url.getProtocol().equals("file")) { | ||
| return Collections.emptyList(); | ||
| } | ||
|
|
||
| List<String> results = new ArrayList<>(findAllFilesUnderClassNameSubDir(c, url, PAYLOADS_FOLDER)); | ||
| Collections.sort(results); | ||
| return results; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the project-relative paths of test files for the specified test class. The files should be underneath a | ||
| * "payloads" subdirectory of the test class directory. Additional subdirectories can exist within the payloads | ||
| * directory itself, and any files found within will be included in the results. | ||
| * | ||
| * @param c test class for which to perform the search | ||
| * @return list of project-relative test file paths | ||
| */ | ||
| public List<String> findAllAnswerFilesFor(Class<?> c) { | ||
| URL url = this.which(c); | ||
| if (url == null || !url.getProtocol().equals("file")) { | ||
| return Collections.emptyList(); | ||
| } | ||
|
|
||
| List<String> results = findAllFilesUnderClassNameSubDir(c, url, ANSWERS_FOLDER, IS_XML_FILE); | ||
| Collections.sort(results); | ||
| return results; | ||
| } | ||
|
|
||
| /** | ||
| * Finds all files beneath the specified subdirectory of the test class resource folder | ||
| * | ||
| * @param c test class for which the resource files exist | ||
| * @param url location from which the classLoader looded the test class | ||
| * @param subDirName subdirectory that contains the files | ||
| * @return List of test resource file paths | ||
| */ | ||
| private List<String> findAllFilesUnderClassNameSubDir(Class<?> c, URL url, final String subDirName) { | ||
| return findAllFilesUnderClassNameSubDir(c, url, subDirName, StringUtils::isNotBlank); | ||
| } | ||
|
|
||
| /** | ||
| * Finds the files beneath a given test class resource folder, filtered by a provided {@link Predicate<String>} | ||
| * | ||
| * @param c test class for which the resource files exist | ||
| * @param url location from which the classLoader loaded the test class | ||
| * @param subDirName subdirectory that contains the files | ||
| * @param fileFilter Predicate used to filter the list of discovered files | ||
| * @return List of test resource file paths | ||
| */ | ||
| private List<String> findAllFilesUnderClassNameSubDir(Class<?> c, URL url, final String subDirName, final Predicate<String> fileFilter) { | ||
| String classNameInPathFormat = getResourceName(c); | ||
| Path subDir = Path.of(getFullPathOfTestClassResourceFolder(url, c), subDirName); | ||
| File testClassDir = subDir.toFile(); | ||
| if (testClassDir.exists() && testClassDir.isDirectory()) { | ||
| try (Stream<Path> theList = Files.walk(testClassDir.toPath())) { | ||
| return theList.filter(Files::isRegularFile) | ||
| .map(testClassDir.toPath()::relativize) | ||
| .map(filePath -> classNameInPathFormat + "/" + subDirName + "/" + filePath) | ||
| .filter(fileFilter::test) | ||
| .collect(Collectors.toList()); | ||
|
|
||
| } catch (IOException e) { | ||
| logger.debug("Failed to retrieve files for class {}", c.getName(), e); | ||
| } | ||
| } | ||
|
|
||
| return Collections.emptyList(); | ||
| } | ||
|
|
||
| /** | ||
| * Gets the absolute path of a test class runtime resource folder | ||
| * | ||
| * @param url URL from which the ClassLoader loaded the test class | ||
| * @param c test class | ||
| * @return test class folder path | ||
| */ | ||
| protected String getFullPathOfTestClassResourceFolder(URL url, Class<?> c) { | ||
| String classNameInPathFormat = getResourceName(c); | ||
| if (url.getPath().contains(CLASS_SUFFIX)) { | ||
| // return the URL minus the ".class" suffix | ||
| return StringUtils.substringBeforeLast(url.getPath(), CLASS_SUFFIX); | ||
| } | ||
|
|
||
| return StringUtils.join(url.getPath(), "/", classNameInPathFormat); | ||
| } | ||
| } | ||
86 changes: 86 additions & 0 deletions
86
src/test/java/emissary/util/io/GreedyResourceReaderTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| package emissary.util.io; | ||
|
|
||
| import emissary.test.core.junit5.UnitTest; | ||
|
|
||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.nio.file.Path; | ||
| import java.util.Arrays; | ||
| import java.util.List; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.junit.jupiter.api.Assertions.assertFalse; | ||
| import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||
| import static org.junit.jupiter.api.Assertions.fail; | ||
|
|
||
| public class GreedyResourceReaderTest extends UnitTest { | ||
|
|
||
|
|
||
| @Test | ||
| void testPayloadFileLocation() { | ||
|
|
||
| // files in the "payloads" subdirectory should be found as resources | ||
| List<String> testFileNames = Arrays.asList("payloads/File1.txt", "payloads/subdir/sample.md"); | ||
| // Non-payload.txt is in the test class directory, but not beneath its "payloads" subdirectory | ||
| final String NON_PAYLOAD = "Non-payload.txt"; | ||
|
|
||
| GreedyResourceReader grr = new GreedyResourceReader(); | ||
| String testClassDir = grr.getResourceName(this.getClass()); | ||
|
|
||
| List<String> resources = grr.findAllPayloadFilesFor(this.getClass()); | ||
| assertNotNull(resources, "Resources must not be null"); | ||
| assertEquals(testFileNames.size(), resources.size(), "All data resources not found"); | ||
|
|
||
| testFileNames.stream() | ||
| .map(t -> Path.of(testClassDir, t)) | ||
| .forEach(p -> assertTrue(resources.contains(p.toString()))); | ||
|
|
||
| assertFalse(resources.contains(Path.of(testClassDir, NON_PAYLOAD).toString())); | ||
|
|
||
| for (String rez : resources) { | ||
| try (InputStream is = grr.getResourceAsStream(rez)) { | ||
| assertNotNull(is, "Failed to open " + rez); | ||
| } catch (IOException e) { | ||
| fail("Failed to open " + rez, e); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| @Test | ||
| void testAnswerFileLocation() { | ||
|
|
||
| // files in the "payloads" subdirectory should be found as resources | ||
| List<String> testAnswerFileNames = Arrays.asList("answers/File1.txt.xml", "answers/subdir/sample.md.xml"); | ||
|
|
||
| // files that should NOT be detected as "answer" files based on their locations | ||
| List<String> misplacedAnswerFileNames = Arrays.asList("Non-answer.xml", "answers/README"); | ||
|
|
||
| GreedyResourceReader grr = new GreedyResourceReader(); | ||
| String testClassDir = grr.getResourceName(this.getClass()); | ||
|
|
||
| List<String> answerFiles = grr.findAllAnswerFilesFor(this.getClass()); | ||
| assertNotNull(answerFiles, "Resources must not be null"); | ||
| assertEquals(testAnswerFileNames.size(), answerFiles.size(), "Not all answer files not found"); | ||
|
|
||
| testAnswerFileNames.stream() | ||
| .map(t -> Path.of(testClassDir, t)) | ||
| .forEach(p -> assertTrue(answerFiles.contains(p.toString()))); | ||
|
|
||
| misplacedAnswerFileNames.stream() | ||
| .map(t -> Path.of(testClassDir, t)) | ||
| .forEach(p -> assertFalse(answerFiles.contains(p.toString()))); | ||
|
|
||
|
|
||
| for (String file : answerFiles) { | ||
| try (InputStream is = grr.getResourceAsStream(file)) { | ||
| assertNotNull(is, "Failed to open " + file); | ||
| } catch (IOException e) { | ||
| fail("Failed to open " + file, e); | ||
| } | ||
| } | ||
| } | ||
| } |
1 change: 1 addition & 0 deletions
1
src/test/resources/emissary/util/io/GreedyResourceReaderTest/Non-answer.xml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| <root>file is in wrong directory</root> |
1 change: 1 addition & 0 deletions
1
src/test/resources/emissary/util/io/GreedyResourceReaderTest/Non-payload.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ignore me |
1 change: 1 addition & 0 deletions
1
src/test/resources/emissary/util/io/GreedyResourceReaderTest/answers/File1.txt.xml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| <root>ignore the file content</root> |
1 change: 1 addition & 0 deletions
1
src/test/resources/emissary/util/io/GreedyResourceReaderTest/answers/README
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| not an answer file |
1 change: 1 addition & 0 deletions
1
src/test/resources/emissary/util/io/GreedyResourceReaderTest/answers/subdir/sample.md.xml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| <root>ignore the file content</root> |
1 change: 1 addition & 0 deletions
1
src/test/resources/emissary/util/io/GreedyResourceReaderTest/payloads/File1.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Ignore the content |
1 change: 1 addition & 0 deletions
1
...t/resources/emissary/util/io/GreedyResourceReaderTest/payloads/subdir/sample.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ## this is a sample file |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this class exist in the test package?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could argue either way, but I like it where it is. As a class that supports a test (as opposed to containing tests), it seems reasonable to be in main/util/io.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree it should be part of the tests jar. I don't know why we would need this outside of our test framework. The conventions it implements are test framework conventions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The unit test that is testing this test utility would live side-by-side next to it. I'm ok with that even though it's a bit unconventional.