diff --git a/apache-rat-core/src/main/java/org/apache/rat/commandline/Arg.java b/apache-rat-core/src/main/java/org/apache/rat/commandline/Arg.java index 069009aa9..314e9c276 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/commandline/Arg.java +++ b/apache-rat-core/src/main/java/org/apache/rat/commandline/Arg.java @@ -132,13 +132,13 @@ public enum Arg { .addOption(Option.builder().longOpt("config").hasArgs().argName("File") .desc("File names for system configuration.") .converter(Converters.FILE_CONVERTER) - .type(File.class) + .type(DocumentName.class) .build()) .addOption(Option.builder().longOpt("licenses").hasArgs().argName("File") .desc("File names for system configuration.") .deprecated(DeprecatedAttributes.builder().setSince("0.17").setForRemoval(true).setDescription(StdMsgs.useMsg("--config")).get()) .converter(Converters.FILE_CONVERTER) - .type(File.class) + .type(DocumentName.class) .build()), Arg::doNotExecute ), @@ -177,7 +177,7 @@ public enum Arg { LICENSES_APPROVED_FILE(new OptionGroup().addOption(Option.builder().longOpt("licenses-approved-file").hasArg().argName("File") .desc("Name of file containing comma separated lists of approved License IDs.") .converter(Converters.FILE_CONVERTER) - .type(File.class) + .type(DocumentName.class) .build()), (context, selected) -> context.getConfiguration().addApprovedLicenseIds(processArrayFile(context, selected))), @@ -198,7 +198,7 @@ public enum Arg { FAMILIES_APPROVED_FILE(new OptionGroup().addOption(Option.builder().longOpt("license-families-approved-file").hasArg().argName("File") .desc("Name of file containing comma separated lists of approved family IDs.") .converter(Converters.FILE_CONVERTER) - .type(File.class) + .type(DocumentName.class) .build()), (context, selected) -> context.getConfiguration().addApprovedLicenseCategories(processArrayFile(context, selected)) ), @@ -247,7 +247,7 @@ public enum Arg { .desc("Name of file containing comma separated lists of denied license IDs. " + "These license families will be removed from the list of approved licenses. " + "Once license families are removed they can not be added back.") - .type(File.class) + .type(DocumentName.class) .converter(Converters.FILE_CONVERTER) .build()), (context, selected) -> context.getConfiguration().removeApprovedLicenseCategories(processArrayFile(context, selected))), @@ -294,12 +294,12 @@ public enum Arg { "File names that do not start with '/' are relative to the directory where the " + "argument is located.") .converter(Converters.FILE_CONVERTER) - .type(File.class) + .type(DocumentName.class) .build()), (context, selected) -> { - File[] files = getParsedOptionValues(selected, context.getCommandLine()); - for (File f : files) { - context.getConfiguration().addSource(f); + DocumentName[] documentNames = getParsedOptionValues(selected, context.getCommandLine()); + for (DocumentName documentName : documentNames) { + context.getConfiguration().addSource(documentName.asFile()); } }), @@ -340,9 +340,9 @@ public enum Arg { .build()), (context, selected) -> { try { - File excludeFileName = context.getCommandLine().getParsedOptionValue(selected); + DocumentName excludeFileName = context.getCommandLine().getParsedOptionValue(selected); if (excludeFileName != null) { - context.getConfiguration().addExcludedPatterns(ExclusionUtils.asIterable(excludeFileName, "#")); + context.getConfiguration().addExcludedPatterns(ExclusionUtils.asIterable(excludeFileName.asFile(), "#")); } } catch (Exception e) { throw ConfigurationException.from(e); @@ -422,9 +422,9 @@ public enum Arg { .build()), (context, selected) -> { try { - File includeFileName = context.getCommandLine().getParsedOptionValue(selected); + DocumentName includeFileName = context.getCommandLine().getParsedOptionValue(selected); if (includeFileName != null) { - context.getConfiguration().addIncludedPatterns(ExclusionUtils.asIterable(includeFileName, "#")); + context.getConfiguration().addIncludedPatterns(ExclusionUtils.asIterable(includeFileName.asFile(), "#")); } } catch (Exception e) { throw ConfigurationException.from(e); @@ -605,22 +605,23 @@ public enum Arg { .addOption(Option.builder().option("o").longOpt("out").hasArg().argName("File") .desc("Define the output file where to write a report to.") .deprecated(DeprecatedAttributes.builder().setSince("0.17").setForRemoval(true).setDescription(StdMsgs.useMsg("--output-file")).get()) - .type(File.class) + .type(DocumentName.class) .converter(Converters.FILE_CONVERTER) .build()) .addOption(Option.builder().longOpt("output-file").hasArg().argName("File") .desc("Define the output file where to write a report to.") - .type(File.class) + .type(DocumentName.class) .converter(Converters.FILE_CONVERTER) .build()), (context, selected) -> { try { - File file = context.getCommandLine().getParsedOptionValue(selected); - File parent = file.getParentFile(); + DocumentName documentName = context.getCommandLine().getParsedOptionValue(selected); + File document = documentName.asFile(); + File parent = document.getParentFile(); if (!parent.mkdirs() && !parent.isDirectory()) { - DefaultLog.getInstance().error("Could not create report parent directory " + file); + DefaultLog.getInstance().error("Could not create report parent directory " + documentName); } - context.getConfiguration().setOut(file); + context.getConfiguration().setOut(document); } catch (ParseException e) { // we write to system out by default. context.logParseException(e, selected, "System.out"); @@ -821,7 +822,8 @@ private static List processArrayArg(final ArgumentContext context, final * @return Option as a file. */ private static File commandLineFile(final ArgumentContext context, final Option selected) throws ParseException { - return context.getCommandLine().getParsedOptionValue(selected); + DocumentName documentName = context.getCommandLine().getParsedOptionValue(selected); + return documentName.asFile(); } /** @@ -859,9 +861,9 @@ private static void processConfigurationArgs(final ArgumentContext context, fina optionCollection.getSelected(CONFIGURATION).ifPresent( selected -> { - File[] files = getParsedOptionValues(selected, context.getCommandLine()); - for (File file : files) { - defaultBuilder.add(file); + DocumentName[] documentNames = getParsedOptionValues(selected, context.getCommandLine()); + for (DocumentName documentName : documentNames) { + defaultBuilder.add(documentName.asFile()); } }); optionCollection.getSelected(CONFIGURATION_NO_DEFAULTS).ifPresent(selected -> { diff --git a/apache-rat-core/src/main/java/org/apache/rat/commandline/Converters.java b/apache-rat-core/src/main/java/org/apache/rat/commandline/Converters.java index 15836663e..15582168c 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/commandline/Converters.java +++ b/apache-rat-core/src/main/java/org/apache/rat/commandline/Converters.java @@ -18,9 +18,8 @@ */ package org.apache.rat.commandline; -import java.io.File; -import java.io.IOException; import java.util.Arrays; +import java.util.Optional; import org.apache.commons.cli.Converter; import org.apache.commons.lang3.tuple.Pair; @@ -73,14 +72,15 @@ private Converters() { /** * A converter that can handle relative or absolute files. */ - public static final class FileConverter implements Converter { + public static final class FileConverter implements Converter { /** The working directory to resolve relative files against. */ private DocumentName workingDirectory; /** * The constructor. + * visible for testing */ - private FileConverter() { + FileConverter() { // private construction only. } @@ -95,24 +95,27 @@ public void setWorkingDirectory(final DocumentName workingDirectory) { /** * Applies the conversion function to the specified file name. * @param fileName the file name to create a file from. - * @return a File. + * @return the DocumentName * @throws NullPointerException if {@code fileName} is null. */ - public File apply(final String fileName) throws NullPointerException { - File file = new File(fileName); - // is this a relative file? - if (!fileName.startsWith(File.separator)) { - // check for a root provided (e.g. C:\\)" - if (!DocumentName.FSInfo.getDefault().rootFor(fileName).isPresent()) { - // no root, resolve against workingDirectory - file = new File(workingDirectory.resolve(fileName).getName()).getAbsoluteFile(); + public DocumentName apply(final String fileName) throws NullPointerException { + DocumentName.FSInfo fsInfo = workingDirectory.fsInfo(); + DocumentName.Builder builder = DocumentName.builder(fsInfo); + String normalizedFileName = fsInfo.normalize(fileName.trim()); + + Optional root = fsInfo.rootFor(normalizedFileName); + builder.setRoot(root.orElse(workingDirectory.getRoot())); + if (fsInfo.startsWithRootOrSeparator(normalizedFileName)) { + if (normalizedFileName.startsWith(workingDirectory.getName()) || + normalizedFileName.startsWith(workingDirectory.getName().substring(workingDirectory.getRoot().length()))) { + builder.setBaseName(workingDirectory.getBaseDocumentName()); + } else { + builder.setBaseName(fsInfo.dirSeparator()); } + } else { + builder.setBaseName(workingDirectory); } - try { - return file.getCanonicalFile(); - } catch (IOException e) { - return file.getAbsoluteFile(); - } + return builder.setName(normalizedFileName).build(); } } } diff --git a/apache-rat-core/src/main/java/org/apache/rat/document/ArchiveEntryName.java b/apache-rat-core/src/main/java/org/apache/rat/document/ArchiveEntryName.java index 13b4668e6..df079bf18 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/document/ArchiveEntryName.java +++ b/apache-rat-core/src/main/java/org/apache/rat/document/ArchiveEntryName.java @@ -23,18 +23,33 @@ import java.nio.file.Paths; import java.util.Collections; +/** + * The DocumentName for an ArchiveEntry. + */ public class ArchiveEntryName extends DocumentName { /** The name of the document that contains this entry. */ private final DocumentName archiveFileName; + /** + * Sets the builder so that a propery DocumentName is constructed. + * @param archiveFileName the archvie file DocumentName + * @param archiveEntryName the entry name + * @return the DocumentName.Builder for the archive entry. + */ private static DocumentName.Builder prepareBuilder(final DocumentName archiveFileName, final String archiveEntryName) { - String root = archiveFileName.getName() + "#"; + String root = archiveFileName.getName() + "#/"; FSInfo fsInfo = new FSInfo("archiveEntry", "/", true, Collections.singletonList(root)); return DocumentName.builder(fsInfo) .setRoot(root) - .setBaseName(root + "/") + .setBaseName("/") .setName(archiveEntryName); } + + /** + * Constucts an archive file name from an archive file document name and an entry name. + * @param archiveFileName the archive file document name. + * @param archiveEntryName the archive entry name. + */ public ArchiveEntryName(final DocumentName archiveFileName, final String archiveEntryName) { super(prepareBuilder(archiveFileName, archiveEntryName)); this.archiveFileName = archiveFileName; @@ -60,25 +75,10 @@ public String getBaseName() { return archiveFileName.getName() + "#"; } - @Override - boolean startsWithRootOrSeparator(final String candidate, final String root, final String separator) { - return super.startsWithRootOrSeparator(candidate, root, separator); - } - @Override public String localized(final String dirSeparator) { String superLocal = super.localized(dirSeparator); superLocal = superLocal.substring(superLocal.lastIndexOf("#") + 1); return archiveFileName.localized(dirSeparator) + "#" + superLocal; } - - @Override - public boolean equals(final Object other) { - return super.equals(other); - } - - @Override - public int hashCode() { - return super.hashCode(); - } } diff --git a/apache-rat-core/src/main/java/org/apache/rat/document/DocumentName.java b/apache-rat-core/src/main/java/org/apache/rat/document/DocumentName.java index c2c0510e9..ffdc90e62 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/document/DocumentName.java +++ b/apache-rat-core/src/main/java/org/apache/rat/document/DocumentName.java @@ -27,17 +27,15 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.CompareToBuilder; -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; @@ -45,8 +43,8 @@ * The name for a document. The {@code DocumentName} is an immutable structure that handles all the intricacies of file * naming on various operating systems. DocumentNames have several components: *
    - *
  • {@code root} - where in the file system the name starts (e.g C: on windows). May be empty but not null.
  • - *
  • {@code dirSeparator} - the separator between name segments (e.g. "\\" on windows, "/" on linux). May not be + *
  • {@code root} - where in the file system the name starts (e.g C:\ on Microsoft Windows). May be empty but not null.
  • + *
  • {@code dirSeparator} - the separator between name segments (e.g. "\" on MicroSoft Windows, "/" on linux). May not be * empty or null.
  • *
  • {@code name} - the name of the file relative to the {@code root}. May not be null. Does NOT begin with a {@code dirSeparator}
  • *
  • {@code baseName} - the name of a directory or file from which this file is reported. A DocumentName with a @@ -65,7 +63,7 @@ public class DocumentName implements Comparable { private final DocumentName baseName; /** The file system info for this document. */ private final FSInfo fsInfo; - /** The root for the DocumentName. May be empty but not null. */ + /** The root for the DocumentName. May be empty but not null. Must be one of the roots in fsInfo*/ private final String root; /** @@ -126,7 +124,7 @@ public static Builder builder(final DocumentName documentName) { } /** - * Creates a file from the document name. + * Creates a file from the fully qualified document name. * @return a new File object. */ public File asFile() { @@ -134,7 +132,8 @@ public File asFile() { } /** - * Creates a path from the document name. + * Creates a path from the document name. This method uses the fullyqualified name without the root. + * this results in a relative file name from the root. * @return a new Path object. */ public Path asPath() { @@ -144,9 +143,23 @@ public Path asPath() { /** * Creates a new DocumentName by adding the child to the current name. * Resulting documentName will have the same base name. + * Directory separator is normalized to the directory separator for this file system. + * If the child string: + *
    + *
    Is blank
    + *
    This DocumentName is returned.
    + *
    Starts with the file system root
    + *
    The root The root must match the root of this DocumentName and the directory structure + * must start with the directory structure of the basename for this DocuemntName.
    + *
    Starts with the directory separator character
    + *
    Result will be a tree starting at the directory specified by the basename.
    + *
    Does not start with a directory separator character
    + *
    Result will be a tree starting at the directory specified by this DocumentName
    + *
    * @param child the child to add (must use directory separator from this document name). * @return the new document name with the same {@link #baseName}, directory sensitivity and case sensitivity as * this one. + * @throws IllegalArgumentException if the child specifies a different root from this document name. */ public DocumentName resolve(final String child) { if (StringUtils.isBlank(child)) { @@ -156,8 +169,25 @@ public DocumentName resolve(final String child) { String pattern = separator.equals("/") ? child.replace('\\', '/') : child.replace('/', '\\'); + Optional expectedRoot = fsInfo.rootFor(child); + if (expectedRoot.isPresent()) { + if (!expectedRoot.get().equals(getRoot())) { + throw new IllegalArgumentException(String.format("%s does not start with %s", pattern, getName())); + } + if (!getRoot().equals(separator)) { + // we have something like C:\ as the root so convert the pattern to start with the separator. + pattern = separator + pattern.substring(getRoot().length()); + if (pattern.startsWith(baseName.name)) { + pattern = pattern.substring(baseName.name.length()); + } + } + } + + // patterns with separators either start with the name of this document plus a relative + // name, or are just directory off the baseName. In either case the name is correct. + // so just handle the relative case. if (!pattern.startsWith(separator)) { - pattern = name + separator + pattern; + pattern = name + separator + pattern; } return new Builder(this).setName(fsInfo.normalize(pattern)).build(); @@ -168,7 +198,15 @@ public DocumentName resolve(final String child) { * @return the fully qualified name of the document. */ public String getName() { - return root + fsInfo.dirSeparator() + name; + return root + name; + } + + /** + * Gets the path of the document. THis is the fully qualified name without the root but starting with a path separator. + * @return the path of the document. + */ + public String getPath() { + return getDirectorySeparator() + name; } /** @@ -203,6 +241,14 @@ public String getDirectorySeparator() { return fsInfo.dirSeparator(); } + /** + * Returns the FSInfo for this document name. + * @return the FSInfo for this document name. + */ + public FSInfo fsInfo() { + return fsInfo; + } + /** * Determines if the candidate starts with the root or separator strings. * @param candidate the candidate to check. If blank method will return {@code false}. @@ -210,7 +256,7 @@ public String getDirectorySeparator() { * @param separator the separator to check. If blank the check is skipped. * @return true if either the root or separator check returned {@code true}. */ - boolean startsWithRootOrSeparator(final String candidate, final String root, final String separator) { + static boolean startsWithRootOrSeparator(final String candidate, final String root, final String separator) { if (StringUtils.isBlank(candidate)) { return false; } @@ -287,19 +333,30 @@ public String toString() { @Override public int compareTo(final DocumentName other) { - return CompareToBuilder.reflectionCompare(this, other); + return new CompareToBuilder() + .append(this.root, other.root) + .append(this.getBaseName(), other.getBaseName()) + .append(this.getName(), other.getName()).build(); } @Override - public boolean equals(final Object other) { - return EqualsBuilder.reflectionEquals(this, other); + public final boolean equals(final Object other) { + if (other instanceof DocumentName otherDocumentName) { + return compareTo(otherDocumentName) == 0; + } + return false; } @Override - public int hashCode() { - return HashCodeBuilder.reflectionHashCode(this); + public final int hashCode() { + return getName().hashCode(); } + /** + * The File System Info Data for a DocumentName. + * Use to preserve data across DocumentNames without having to + * reconstruct the data for each DocumentName. + */ private static final class FSInfoData { /** The case sensitivity flag */ private final boolean isCaseSensitive; @@ -468,6 +525,14 @@ public Optional rootFor(final String name) { return Optional.empty(); } + /** + * Gets the array of roots for this file system. + * @return an array of roots for this file system. + */ + public String[] roots() { + return data.roots.toArray(new String[0]); + } + /** * Tokenizes the string based on the directory separator of this DocumentName. * @param source the source to tokenize. @@ -483,38 +548,97 @@ public String[] tokenize(final String source) { * @return the normalized file name. */ public String normalize(final String pattern) { - if (StringUtils.isBlank(pattern)) { + if (StringUtils.isBlank(pattern) || pattern.trim().equals(".")) { return ""; } - List parts = new ArrayList<>(Arrays.asList(tokenize(pattern))); - for (int i = 0; i < parts.size(); i++) { + String adjustedPattern = dirSeparator().equals("/") ? pattern.replace("\\", "/") : pattern.replace("/", "\\"); + if (adjustedPattern.trim().equals(dirSeparator())) { + return adjustedPattern; + } + List parts = new ArrayList<>(Arrays.asList(tokenize(adjustedPattern))); + int i = 0; + while (i < parts.size()) { String part = parts.get(i); if (part.equals("..")) { if (i == 0) { throw new IllegalStateException("Unable to create path before root"); } - parts.set(i - 1, null); - parts.set(i, null); + parts.remove(i); + parts.remove(i - 1); + i--; } else if (part.equals(".")) { - parts.set(i, null); + parts.remove(i); + } else { + i++; + } + } + if (parts.isEmpty()) { + throw new IllegalStateException("Unable to create path before root"); + } + return String.join(dirSeparator(), parts); + } + + /** + * Creates a path separated by the directory separator. + * Starting with an empty string will cause the directory separateor to appear at the beginning. + * @param segments the segments that make up the path. + * @return the path string. + */ + public String mkPath(final String... segments) { + return String.join(dirSeparator(), segments); + } + + /** + * Determines if the candidate string starts with a root or directory separator as defined in this + * FSInfo. + * @param candidate the candidate string to test. + * @return {@code true} if the candidate starts with a root or a directory separator. + */ + public boolean startsWithRootOrSeparator(final String candidate) { + if (candidate == null) { + return false; + } + String target = candidate.trim(); + if (StringUtils.isBlank(target)) { + return false; + } + for (String root : roots()) { + if (target.startsWith(root)) { + return true; } } - return parts.stream().filter(Objects::nonNull).collect(Collectors.joining(dirSeparator())); + return target.startsWith(dirSeparator()); + } + + private int compareData(final DocumentName.FSInfoData otherData) { + int result = Boolean.compare(this.data.isCaseSensitive, otherData.isCaseSensitive); + if (result == 0) { + result = this.data.separator.compareTo(otherData.separator); + if (result == 0) { + if (new HashSet<>(this.data.roots).containsAll(otherData.roots)) { + result = new HashSet<>(otherData.roots).containsAll(this.data.roots) ? 0 : 1; + } else { + result = -1; + } + } + } + return result; } @Override public int compareTo(final FSInfo other) { - return CompareToBuilder.reflectionCompare(this, other); + int result = this.name.compareToIgnoreCase(other.name); + return result == 0 ? compareData(other.data) : result; } @Override public boolean equals(final Object other) { - return EqualsBuilder.reflectionEquals(this, other); + return other instanceof FSInfo oth && this.compareTo(oth) == 0; } @Override public int hashCode() { - return HashCodeBuilder.reflectionHashCode(this); + return name.hashCode(); } } @@ -538,7 +662,7 @@ public static final class Builder { */ private Builder(final FSInfo fsInfo) { this.fsInfo = fsInfo; - root = ""; + this.root = ""; } /** @@ -596,6 +720,16 @@ private void verify() { } if (!sameNameFlag) { Objects.requireNonNull(baseName, "Basename must not be null"); + if (this.root.isBlank()) { + this.root = this.baseName.getRoot(); + } + } + if (this.root.isBlank()) { + this.root = this.fsInfo.roots()[0]; + } else { + if (!List.of(this.fsInfo.roots()).contains(this.root)) { + throw new IllegalArgumentException(String.format("'%s' is not a valid root for %s", this.root, this.fsInfo)); + } } } @@ -622,15 +756,9 @@ public Builder setRoot(final String root) { public Builder setName(final String name) { Pair pair = splitRoot(StringUtils.defaultIfBlank(name, "")); if (this.root.isEmpty()) { - this.root = pair.getLeft(); + setRoot(pair.getLeft()); } this.name = fsInfo.normalize(pair.getRight()); - if (this.baseName != null && !baseName.name.isEmpty()) { - if (!this.name.startsWith(baseName.name)) { - this.name = this.name.isEmpty() ? baseName.name : - baseName.name + fsInfo.dirSeparator() + this.name; - } - } return this; } @@ -644,19 +772,11 @@ public Builder setName(final String name) { */ Pair splitRoot(final String name) { String workingName = name; - Optional maybeRoot = fsInfo.rootFor(name); - String root = maybeRoot.orElse(""); - if (!root.isEmpty()) { - if (workingName.startsWith(root)) { - workingName = workingName.substring(root.length()); - if (!workingName.startsWith(fsInfo.dirSeparator())) { - if (root.endsWith(fsInfo.dirSeparator())) { - root = root.substring(0, root.length() - fsInfo.dirSeparator().length()); - } - } - } + String workingRoot = fsInfo.rootFor(name).orElse(""); + if (!workingRoot.isEmpty() && workingName.startsWith(workingRoot)) { + workingName = workingName.substring(workingRoot.length()); } - return ImmutablePair.of(root, workingName); + return ImmutablePair.of(workingRoot, workingName); } /** @@ -745,12 +865,38 @@ public Builder setBaseName(final File file) { return this; } + // only called if basName is not null + private void verifyBaseName() { + if (!this.name.startsWith(baseName.name)) { + this.name = this.name.isEmpty() ? baseName.name : + baseName.name + fsInfo.dirSeparator() + this.name; + } + if (!this.baseName.getRoot().equals(root)) { + Builder builder = new Builder(baseName).setRoot(root); + if (baseName.baseName != null && baseName.baseName != baseName) { + builder.setBaseName(baseName.baseName); + } else { + builder.baseName = null; + builder.sameNameFlag = true; + } + this.baseName = builder.build(); + } + + } + /** * Build a DocumentName from this builder. * @return A new DocumentName. */ public DocumentName build() { verify(); + if (this.baseName != null) { + verifyBaseName(); + } else { + if (this.name.startsWith(root)) { + this.name = this.name.substring(root.length()); + } + } return new DocumentName(this); } } diff --git a/apache-rat-core/src/test/java/org/apache/rat/OptionCollectionTest.java b/apache-rat-core/src/test/java/org/apache/rat/OptionCollectionTest.java index 64c11257a..0c7846303 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/OptionCollectionTest.java +++ b/apache-rat-core/src/test/java/org/apache/rat/OptionCollectionTest.java @@ -135,6 +135,14 @@ public String toString() { public @interface TestFunction { } + /** + * Gets the test path for. + * @return the test path. + */ + public static Path getTestPath() { + return testPath; + } + /** * Process methods in a test provider. * Tests are detected by looking for the {@link TestFunction} annotation. @@ -221,7 +229,8 @@ public void testDefaultConfiguration() throws ParseException { @ValueSource(strings = { ".", "./", "target", "./target" }) public void getReportableTest(String fName) throws IOException { File base = new File(fName); - String expected = DocumentName.FSInfo.getDefault().normalize(base.getAbsolutePath()); + DocumentName.FSInfo fsInfo = new DocumentName.FSInfo(testPath.getFileSystem()); + String expected = fsInfo.normalize(base.getAbsolutePath()); ReportConfiguration config = OptionCollection.parseCommands(testPath.toFile(), new String[]{fName}, o -> fail("Help called"), false); IReportable reportable = OptionCollection.getReportable(base, config); assertThat(reportable).as(() -> format("'%s' returned null", fName)).isNotNull(); diff --git a/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsProvider.java b/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsProvider.java index 84d21a8c6..1017311ec 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsProvider.java +++ b/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsProvider.java @@ -115,6 +115,13 @@ protected final ReportConfiguration generateConfig(List> return config; } + private File configureRatDir(Option option) { + configureSourceDir(option); + File result = new File(sourceDir, ".rat"); + FileUtils.mkDir(result); + return result; + } + private void configureSourceDir(Option option) { sourceDir = new File(baseDir, OptionFormatter.getName(option)); FileUtils.mkDir(sourceDir); @@ -226,9 +233,10 @@ protected void licensesDeniedTest() { @OptionCollectionTest.TestFunction protected void licensesDeniedFileTest() { - File outputFile = FileUtils.writeFile(baseDir, "licensesDenied.txt", Collections.singletonList("ILLUMOS")); - execLicensesDeniedTest(Arg.LICENSES_DENIED_FILE.find("licenses-denied-file"), - new String[]{outputFile.getAbsolutePath()}); + Option option = Arg.LICENSES_DENIED_FILE.find("licenses-denied-file"); + File ratDir = configureRatDir(option); + File outputFile = FileUtils.writeFile(ratDir, "licensesDenied.txt", Collections.singletonList("ILLUMOS")); + execLicensesDeniedTest(option, new String[]{outputFile.getAbsolutePath()}); } private void noDefaultsTest(final Option option) { @@ -318,7 +326,7 @@ protected void counterMinTest() { } // exclude tests - private void execExcludeTest(final Option option, final String[] args) { + private void execExcludeTest(final Option option, final String[] args, final boolean addIgnored) { String[] notExcluded = {"notbaz", "well._afile"}; String[] excluded = {"some.foo", "B.bar", "justbaz"}; try { @@ -333,23 +341,23 @@ private void execExcludeTest(final Option option, final String[] args) { Reporter reporter = new Reporter(config); ClaimStatistic claimStatistic = reporter.execute(); assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(5); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0); + assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(addIgnored ? 1 : 0); // filter out source config = generateConfig(ImmutablePair.of(option, args)); reporter = new Reporter(config); claimStatistic = reporter.execute(); assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(3); + assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(addIgnored ? 4 : 3); } catch (IOException | RatException e) { fail(e.getMessage(), e); } } private void excludeFileTest(final Option option) { - configureSourceDir(option); - File outputFile = FileUtils.writeFile(baseDir, "exclude.txt", Arrays.asList(EXCLUDE_ARGS)); - execExcludeTest(option, new String[]{outputFile.getAbsolutePath()}); + File ratDir = configureRatDir(option); + File outputFile = FileUtils.writeFile(ratDir, "exclude.txt", Arrays.asList(EXCLUDE_ARGS)); + execExcludeTest(option, new String[]{outputFile.getAbsolutePath()}, true); } @OptionCollectionTest.TestFunction @@ -364,12 +372,12 @@ protected void inputExcludeFileTest() { @OptionCollectionTest.TestFunction protected void excludeTest() { - execExcludeTest(Arg.EXCLUDE.find("exclude"), EXCLUDE_ARGS); + execExcludeTest(Arg.EXCLUDE.find("exclude"), EXCLUDE_ARGS, false); } @OptionCollectionTest.TestFunction protected void inputExcludeTest() { - execExcludeTest(Arg.EXCLUDE.find("input-exclude"), EXCLUDE_ARGS); + execExcludeTest(Arg.EXCLUDE.find("input-exclude"), EXCLUDE_ARGS, false); } @OptionCollectionTest.TestFunction @@ -495,7 +503,7 @@ protected void inputExcludeParsedScmTest() { } // include tests - private void execIncludeTest(final Option option, final String[] args) { + private void execIncludeTest(final Option option, final String[] args, boolean addIgnored) { Option excludeOption = Arg.EXCLUDE.option(); String[] notExcluded = {"B.bar", "justbaz", "notbaz"}; String[] excluded = {"some.foo"}; @@ -510,7 +518,7 @@ private void execIncludeTest(final Option option, final String[] args) { Reporter reporter = new Reporter(config); ClaimStatistic claimStatistic = reporter.execute(); assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(4); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0); + assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(addIgnored ? 1 : 0); // verify exclude removes most files. config = generateConfig(ImmutablePair.of(excludeOption, EXCLUDE_ARGS)); @@ -518,7 +526,7 @@ private void execIncludeTest(final Option option, final String[] args) { claimStatistic = reporter.execute(); assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); // .gitignore is ignored by default as it is hidden but not counted - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(3); + assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(addIgnored ? 4 : 3); // verify include pust them back config = generateConfig(ImmutablePair.of(option, args), ImmutablePair.of(excludeOption, EXCLUDE_ARGS)); @@ -526,15 +534,16 @@ private void execIncludeTest(final Option option, final String[] args) { claimStatistic = reporter.execute(); assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3); // .gitignore is ignored by default as it is hidden but not counted - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(1); + assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(addIgnored ? 2 : 1); } catch (IOException | RatException e) { fail(e.getMessage(), e); } } private void includeFileTest(final Option option) { - File outputFile = FileUtils.writeFile(baseDir, "include.txt", Arrays.asList(INCLUDE_ARGS)); - execIncludeTest(option, new String[]{outputFile.getAbsolutePath()}); + File ratDir = configureRatDir(option); + File outputFile = FileUtils.writeFile(ratDir, "include.txt", Arrays.asList(INCLUDE_ARGS)); + execIncludeTest(option, new String[]{outputFile.getAbsolutePath()}, true); } @OptionCollectionTest.TestFunction @@ -549,12 +558,12 @@ protected void includesFileTest() { @OptionCollectionTest.TestFunction protected void includeTest() { - execIncludeTest(Arg.INCLUDE.find("include"), INCLUDE_ARGS); + execIncludeTest(Arg.INCLUDE.find("include"), INCLUDE_ARGS, false); } @OptionCollectionTest.TestFunction protected void inputIncludeTest() { - execIncludeTest(Arg.INCLUDE.find("input-include"), INCLUDE_ARGS); + execIncludeTest(Arg.INCLUDE.find("input-include"), INCLUDE_ARGS, false); } @OptionCollectionTest.TestFunction @@ -655,7 +664,7 @@ private void execLicenseFamiliesApprovedTest(final Option option, final String[] @OptionCollectionTest.TestFunction protected void licenseFamiliesApprovedFileTest() { Option option = Arg.FAMILIES_APPROVED_FILE.find("license-families-approved-file"); - File outputFile = FileUtils.writeFile(baseDir, "familiesApproved.txt", Collections.singletonList("catz")); + File outputFile = FileUtils.writeFile(configureRatDir(option), "familiesApproved.txt", Collections.singletonList("catz")); execLicenseFamiliesApprovedTest(option, new String[]{outputFile.getAbsolutePath()}); } @@ -694,9 +703,9 @@ private void execLicenseFamiliesDeniedTest(final Option option, final String[] a @OptionCollectionTest.TestFunction protected void licenseFamiliesDeniedFileTest() { - File outputFile = FileUtils.writeFile(baseDir, "familiesDenied.txt", Collections.singletonList("BSD-3")); - execLicenseFamiliesDeniedTest(Arg.FAMILIES_DENIED_FILE.find("license-families-denied-file"), - new String[]{outputFile.getAbsolutePath()}); + Option option = Arg.FAMILIES_DENIED_FILE.find("license-families-denied-file"); + File outputFile = FileUtils.writeFile(configureRatDir(option), "familiesDenied.txt", Collections.singletonList("BSD-3")); + execLicenseFamiliesDeniedTest(option, new String[]{outputFile.getAbsolutePath()}); } @OptionCollectionTest.TestFunction @@ -712,7 +721,6 @@ private void configTest(final Option option) { String[] args = { Resources.getResourceFile("OptionTools/One.xml").getAbsolutePath(), Resources.getResourceFile("OptionTools/Two.xml").getAbsolutePath()}; - Pair arg1 = ImmutablePair.of(option, args); writeFile("bsd.txt", "SPDX-License-Identifier: BSD-3-Clause"); @@ -788,9 +796,10 @@ protected void execLicensesApprovedTest(final Option option, String[] args) { @OptionCollectionTest.TestFunction protected void licensesApprovedFileTest() { - File outputFile = FileUtils.writeFile(baseDir, "licensesApproved.txt", Collections.singletonList("GPL1")); - execLicensesApprovedTest(Arg.LICENSES_APPROVED_FILE.find("licenses-approved-file"), - new String[]{outputFile.getAbsolutePath()}); + Option option = Arg.LICENSES_APPROVED_FILE.find("licenses-approved-file"); + File ratDir = configureRatDir(option); + File outputFile = FileUtils.writeFile(ratDir, "licensesApproved.txt", Collections.singletonList("GPL1")); + execLicensesApprovedTest(option, new String[]{outputFile.getAbsolutePath()}); } @OptionCollectionTest.TestFunction diff --git a/apache-rat-core/src/test/java/org/apache/rat/ReporterTest.java b/apache-rat-core/src/test/java/org/apache/rat/ReporterTest.java index 39d780c9e..3d9c84e7c 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/ReporterTest.java +++ b/apache-rat-core/src/test/java/org/apache/rat/ReporterTest.java @@ -209,9 +209,10 @@ public void testXMLOutput() throws Exception { expected.put("/tri.txt", mapOf("encoding", "ISO-8859-1", "mediaType", "text/plain", "type", "STANDARD")); - File output = new File(tempDirectory, "testXMLOutput"); + File output = new File(tempDirectory, ".rat/testXMLOutput"); + output.getParentFile().mkdir(); CommandLine commandLine = new DefaultParser().parse(OptionCollection.buildOptions(), new String[]{"--output-style", "xml", "--output-file", output.getPath(), basedir}); - ArgumentContext ctxt = new ArgumentContext(new File("."), commandLine); + ArgumentContext ctxt = new ArgumentContext(tempDirectory, commandLine); ReportConfiguration config = OptionCollection.createConfiguration(ctxt); new Reporter(config).output(); diff --git a/apache-rat-core/src/test/java/org/apache/rat/commandline/ArgTests.java b/apache-rat-core/src/test/java/org/apache/rat/commandline/ArgTests.java index 1d51a5f29..742632c5a 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/commandline/ArgTests.java +++ b/apache-rat-core/src/test/java/org/apache/rat/commandline/ArgTests.java @@ -7,7 +7,7 @@ * "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 * + * https://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 * @@ -18,7 +18,6 @@ */ package org.apache.rat.commandline; -import java.io.IOException; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Options; @@ -32,6 +31,7 @@ import org.junit.jupiter.params.provider.ValueSource; import java.io.File; +import java.nio.file.Path; import static org.assertj.core.api.Assertions.assertThat; @@ -45,21 +45,31 @@ private CommandLine createCommandLine(String[] args) throws ParseException { @ParameterizedTest(name = "{0}") @ValueSource(strings = { "rat.txt", "./rat.txt", "/rat.txt", "target/rat.test" }) - public void outputFileNameNoDirectoryTest(String name) throws ParseException, IOException { - class OutputFileConfig extends ReportConfiguration { + public void outputFileNameNoDirectoryTest(String name) throws ParseException { + class OutputFileConfig extends ReportConfiguration { private File actual = null; + @Override public void setOut(File file) { actual = file; } } - String fileName = name.replace("/", DocumentName.FSInfo.getDefault().dirSeparator()); - File expected = new File(fileName); - CommandLine commandLine = createCommandLine(new String[] {"--output-file", fileName}); + DocumentName.FSInfo fsInfo = DocumentName.FSInfo.getDefault(); + String fileName = name.replace("/", fsInfo.dirSeparator()); + File localFile = new File("."); + DocumentName localFileName = DocumentName.builder(localFile).build(); + Path workingPath = localFile.getAbsoluteFile().toPath(); + String expected = fsInfo.normalize(workingPath.resolve("./" + fileName).toString()); + + CommandLine commandLine = createCommandLine(new String[]{"--output-file", fileName}); OutputFileConfig configuration = new OutputFileConfig(); - ArgumentContext ctxt = new ArgumentContext(new File("."), configuration, commandLine); + ArgumentContext ctxt = new ArgumentContext(localFile, configuration, commandLine); Arg.processArgs(ctxt, CLIOptionCollection.INSTANCE); - assertThat(configuration.actual.getAbsolutePath()).isEqualTo(expected.getCanonicalPath()); + if (name.equals("/rat.txt")) { + assertThat(fsInfo.normalize(configuration.actual.getAbsolutePath())).isEqualTo(localFileName.getRoot() + "rat.txt"); + } else { + assertThat(fsInfo.normalize(configuration.actual.toString())).isEqualTo(expected); + } } } diff --git a/apache-rat-core/src/test/java/org/apache/rat/commandline/FileConverterTest.java b/apache-rat-core/src/test/java/org/apache/rat/commandline/FileConverterTest.java new file mode 100644 index 000000000..34271b2db --- /dev/null +++ b/apache-rat-core/src/test/java/org/apache/rat/commandline/FileConverterTest.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you 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 * + * * + * https://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 org.apache.rat.commandline; + +import org.apache.rat.document.DocumentName; +import org.apache.rat.document.FSInfoTest; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class FileConverterTest { + + @ParameterizedTest + @MethodSource("applyTestData") + void applyTest(DocumentName.FSInfo fsInfo, String fileName, Converters.FileConverter underTest, String expected) { + assertThat(underTest.apply(fileName).getName()).isEqualTo(expected); + } + + /** + * Record returned from createDocumentData + * @param documentName the document name for the working directory. + * @param defaultRoot the default root for the file system + * @param workingBaseName the working base name. + * @param baseName the base name. + */ + private record DocumentData(DocumentName documentName, String defaultRoot, String workingBaseName, String baseName) { + } + + /** + * Creats a DocumentData from a FSInfo. + * @param fsInfo the FSInfo to work with. + * @return the DocumentData for the working directory,. + */ + private static DocumentData createDocumentData(final DocumentName.FSInfo fsInfo) { + String defaultRoot = fsInfo.roots()[0]; + String workingBaseName = defaultRoot + "working"; + String baseName = workingBaseName + fsInfo.dirSeparator() + "base"; + return new DocumentData(DocumentName.builder(fsInfo).setRoot(defaultRoot).setBaseName(workingBaseName).setName("base").build(), + defaultRoot, workingBaseName, baseName); + } + + + static List applyTestData() { + List lst = new ArrayList<>(); + + for (DocumentName.FSInfo fsInfo : FSInfoTest.TEST_SUITE) { + final DocumentData workingDirectory = createDocumentData(fsInfo); + final Converters.FileConverter underTest = new Converters.FileConverter(); + underTest.setWorkingDirectory(workingDirectory.documentName); + + lst.add(Arguments.of(fsInfo, "/foo/bar.txt", underTest, workingDirectory.defaultRoot + String.join(fsInfo.dirSeparator(), "foo", "bar.txt"))); + lst.add(Arguments.of(fsInfo, "\\foo\\bar.txt", underTest, workingDirectory.defaultRoot + String.join(fsInfo.dirSeparator(), "foo", "bar.txt"))); + + lst.add(Arguments.of(fsInfo, "foo/bar.txt", underTest, workingDirectory.baseName + String.join(fsInfo.dirSeparator(), "", "foo", "bar.txt"))); + lst.add(Arguments.of(fsInfo, "foo\\bar.txt", underTest, workingDirectory.baseName + String.join(fsInfo.dirSeparator(), "", "foo", "bar.txt"))); + + if (fsInfo.equals(FSInfoTest.WINDOWS)) { + String root = fsInfo.roots()[1]; + lst.add(Arguments.of(fsInfo, root + "foo/bar.txt", underTest, root + String.join(fsInfo.dirSeparator(), "foo", "bar.txt"))); + lst.add(Arguments.of(fsInfo, root + "foo\\bar.txt", underTest, root + String.join(fsInfo.dirSeparator(), "foo", "bar.txt"))); + + } + } + return lst; + } +} diff --git a/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/ExclusionProcessorTest.java b/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/ExclusionProcessorTest.java index 85d8d9762..19e2e1542 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/ExclusionProcessorTest.java +++ b/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/ExclusionProcessorTest.java @@ -30,7 +30,6 @@ import java.io.File; import java.io.IOException; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -44,12 +43,10 @@ import static org.apache.rat.document.FSInfoTest.OSX; import static org.apache.rat.document.FSInfoTest.UNIX; import static org.apache.rat.document.FSInfoTest.WINDOWS; +import static org.junit.jupiter.api.Assertions.fail; public class ExclusionProcessorTest { - final private static DocumentNameMatcher TRUE = DocumentNameMatcher.MATCHES_ALL; - final private static DocumentNameMatcher FALSE = DocumentNameMatcher.MATCHES_NONE; - @TempDir private static Path tempDir; @@ -70,10 +67,12 @@ private DocumentName mkName(DocumentName baseDir, String pth) throws IOException String fn = result.localized(FileSystems.getDefault().getSeparator()); File file = tempDir.resolve(fn.substring(1)).toFile(); File parent = file.getParentFile(); - if (parent.exists() && !parent.isDirectory()) { - parent.delete(); + if (parent.exists() && !parent.isDirectory() && !parent.delete()) { + fail(() -> "Unable to delete parent: " + parent); + } + if (!parent.exists() && !parent.mkdirs()) { + fail((() -> "Unable to create parent: " + parent)); } - parent.mkdirs(); if (file.exists()) { if (file.isDirectory()) { FileUtils.deleteDirectory(file); @@ -81,7 +80,9 @@ private DocumentName mkName(DocumentName baseDir, String pth) throws IOException FileUtils.delete(file); } } - file.createNewFile(); + if (!file.createNewFile()) { + fail(() -> "Unable to create file: " + file); + } Mockito.when(mocked.asFile()).thenReturn(file); return mocked; } @@ -246,34 +247,6 @@ void addExcludedPatternsTest(DocumentName basedir) throws IOException { assertExclusions(basedir, "**/foo/**", expectedMap); } - @ParameterizedTest - @MethodSource("getDocumentNames") - void orTest(DocumentName basedir) { - ExclusionProcessor underTest = new ExclusionProcessor(); - assertThat(DocumentNameMatcher.or(Arrays.asList(TRUE, FALSE)).matches(basedir)).isTrue(); - assertThat(DocumentNameMatcher.or(Arrays.asList(FALSE, TRUE)).matches(basedir)).isTrue(); - assertThat(DocumentNameMatcher.or(Arrays.asList(TRUE, TRUE)).matches(basedir)).isTrue(); - assertThat(DocumentNameMatcher.or(Arrays.asList(FALSE, FALSE)).matches(basedir)).isFalse(); - } - - @ParameterizedTest - @MethodSource("getDocumentNames") - void andTest(DocumentName basedir) { - ExclusionProcessor underTest = new ExclusionProcessor(); - assertThat(DocumentNameMatcher.and(TRUE, FALSE).matches(basedir)).isFalse(); - assertThat(DocumentNameMatcher.and(FALSE, TRUE).matches(basedir)).isFalse(); - assertThat(DocumentNameMatcher.and(TRUE, TRUE).matches(basedir)).isTrue(); - assertThat(DocumentNameMatcher.and(FALSE, FALSE).matches(basedir)).isFalse(); - } - - @ParameterizedTest - @MethodSource("getDocumentNames") - void notTest(DocumentName basedir) { - ExclusionProcessor underTest = new ExclusionProcessor(); - assertThat(DocumentNameMatcher.not(TRUE).matches(basedir)).isFalse(); - assertThat(DocumentNameMatcher.not(FALSE).matches(basedir)).isTrue(); - } - private static Stream getDocumentNames() { List lst = new ArrayList<>(); diff --git a/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameBuilderTest.java b/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameBuilderTest.java index 0e0f0d1f7..08f0b0f5b 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameBuilderTest.java +++ b/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameBuilderTest.java @@ -19,23 +19,40 @@ package org.apache.rat.document; import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; + +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.FieldSource; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.apache.rat.document.FSInfoTest.WINDOWS; +/** + * Tests the DcoumentName.Builder class + */ public class DocumentNameBuilderTest { - @ParameterizedTest(name="{0}") - @MethodSource("buildTestData") - void buildTest(String testName, DocumentName documentName, String name, String shortName, String baseName, String root, + private static final DocumentName.FSInfo[] TEST_SUITE = FSInfoTest.TEST_SUITE; + + /** + * Validates the data in a document name matches expected data. + * @param documentName the document name to check + * @param name the expected fully qualified name. + * @param shortName the name for the last segment of the name. + * @param baseName the name of the base document. + * @param root the root the document is in. + * @param directorySeparator the expected directory separator. + * @param isCaseSensitive the expected case sensitivity. + * @param localized the default localized name (e.g. path and file name from base name). + * @param localizedArg the localized name with directory separator set to '+' + */ + void assertDocumentName(DocumentName documentName, String name, String shortName, String baseName, String root, String directorySeparator, Boolean isCaseSensitive, String localized, String localizedArg) { assertThat(documentName.getName()).as("Invalid name").isEqualTo(name); assertThat(documentName.getShortName()).as("Invalid short name").isEqualTo(shortName); @@ -48,120 +65,192 @@ void buildTest(String testName, DocumentName documentName, String name, String s assertThat(documentName.isCaseSensitive()).as("Invalid case sensitivity").isFalse(); } assertThat(documentName.localized()).as("Invalid localized ").isEqualTo(localized); - final String sep = documentName.getDirectorySeparator().equals("/") ? "\\" : "/"; - assertThat(documentName.localized(sep)).as(() -> String.format("Invalid localized('%s')", sep)).isEqualTo(localizedArg); + assertThat(documentName.localized("+")).as("Invalid localized('+')").isEqualTo(localizedArg); } - static Stream buildTestData() { - List lst = new ArrayList<>(); - - // - String testName = "windows\\foo direct"; - DocumentName documentName = DocumentName.builder(WINDOWS).setName("C:\\windows\\foo").setBaseName("C:\\windows").build(); - lst.add(Arguments.of( testName, documentName, "C:\\windows\\foo", "foo", "C:\\windows", "C:", "\\", false, - "\\foo", "/foo")); - DocumentName baseName = documentName; - - // - testName = "builder(docName)"; - documentName = DocumentName.builder(baseName).build(); - lst.add(Arguments.of( testName, documentName, "C:\\windows\\foo", "foo", "C:\\windows", "C:", "\\", false, - "\\foo", "/foo")); - - // - testName = "windows\\foo\\bar by resolve"; - documentName = baseName.resolve("bar"); - lst.add(Arguments.of(testName, documentName, "C:\\windows\\foo\\bar", "bar", "C:\\windows", "C:", "\\", false, - "\\foo\\bar", "/foo/bar")); - - // - testName = "windows\\foo\\direct by basename"; - documentName = DocumentName.builder(baseName).setName("windows\\foo\\direct").build(); - lst.add(Arguments.of(testName, documentName, "C:\\windows\\foo\\direct", "direct", "C:\\windows", "C:", "\\", false, - "\\foo\\direct", "/foo/direct")); - - // - testName = "windows\\foo\\bar by file"; - File file = mock(File.class); - File parent = mock(File.class); - when(file.getAbsolutePath()).thenReturn("C:\\windows\\foo\\bar"); - when(file.getParentFile()).thenReturn(parent); - when(file.isDirectory()).thenReturn(false); - when(parent.getAbsolutePath()).thenReturn("C:\\windows\\foo"); - when(parent.isDirectory()).thenReturn(true); - documentName = new DocumentName.Builder(WINDOWS, file).build(); - lst.add(Arguments.of(testName, documentName, "C:\\windows\\foo\\bar", "bar", "C:\\windows\\foo", "C:", "\\", false, - "\\bar", "/bar")); - - // - testName = "windows\\foo\\bar by directory"; - file = mock(File.class); - parent = mock(File.class); - when(file.getAbsolutePath()).thenReturn("C:\\windows\\foo\\bar"); - when(file.getParentFile()).thenReturn(parent); - when(file.isDirectory()).thenReturn(true); - when(parent.getAbsolutePath()).thenReturn("C:\\windows\\foo"); - when(parent.isDirectory()).thenReturn(true); - documentName = new DocumentName.Builder(WINDOWS, file).build(); - lst.add(Arguments.of(testName, documentName, "C:\\windows\\foo\\bar", "bar", "C:\\windows\\foo\\bar", "C:", "\\", false, - "\\", "/")); - - // - testName = "windows setRoot"; - documentName = DocumentName.builder(baseName).setRoot("D:").build(); - lst.add(Arguments.of(testName, documentName, "D:\\windows\\foo", "foo", "C:\\windows", "D:", "\\", false, - "D:\\windows\\foo", "D:/windows/foo")); - - testName = "windows setRoot(null)"; - documentName = DocumentName.builder(baseName).setRoot(null).build(); - lst.add(Arguments.of(testName, documentName, "\\windows\\foo", "foo", "C:\\windows", "", "\\", false, - "\\windows\\foo", "/windows/foo")); - - testName = "windows setRoot('')"; - documentName = DocumentName.builder(baseName).setRoot("").build(); - lst.add(Arguments.of(testName, documentName, "\\windows\\foo", "foo", "C:\\windows", "", "\\", false, - "\\windows\\foo", "/windows/foo")); - - // - testName = "windows setName('baz')"; - documentName = DocumentName.builder(baseName).setName("baz").build(); - lst.add(Arguments.of(testName, documentName, "C:\\windows\\baz", "baz", "C:\\windows", "C:", "\\", false, - "\\baz", "/baz")); - - testName = "windows setName((String)null)"; - documentName = DocumentName.builder(baseName).setName((String)null).build(); - lst.add(Arguments.of(testName, documentName, "C:\\windows", "windows", "C:\\windows", "C:", "\\", false, - "\\", "/")); - - testName = "windows setName('')"; - documentName = DocumentName.builder(baseName).setName("").build(); - lst.add(Arguments.of(testName, documentName, "C:\\windows", "windows", "C:\\windows", "C:", "\\", false, - "\\", "/")); - - file = mock(File.class); - parent = mock(File.class); - when(file.getAbsolutePath()).thenReturn("C:\\windows\\foo\\bar"); + /** + * Verifies tha the baseName is not modified when used in the builder. + * Base name is default root + OS name. For example C:\windows, or /unix + * @param fsInfo the file system info for the test. + */ + @ParameterizedTest + @FieldSource("TEST_SUITE") + void baseNamePreserved(DocumentName.FSInfo fsInfo) { + final String root = fsInfo.roots()[0]; + final String baseNameStr = root + fsInfo; + // create a document {os name}/bar. Used to establish basename in builder. + final DocumentName siblingName = DocumentName.builder(fsInfo).setName("bar").setBaseName(fsInfo.toString()).build(); + + // check a relative name does not change base name. + String nameStr = fsInfo.mkPath("foo", "baz"); + DocumentName documentName = DocumentName.builder(siblingName).setName(nameStr).build(); + String expected = root + fsInfo.mkPath(fsInfo.toString(), "foo", "baz"); + assertThat(documentName.getName()).as("relative value").isEqualTo(expected); + assertDocumentName(documentName, expected, "baz", baseNameStr, root, fsInfo.dirSeparator(), fsInfo.isCaseSensitive(), + fsInfo.dirSeparator() + nameStr, "+foo+baz"); + + // check a FQName results in the base name not being changed. + documentName = DocumentName.builder(siblingName).setName(expected).build(); + assertThat(documentName.getName()).as("absolute value").isEqualTo(expected); + assertDocumentName(documentName, expected, "baz", baseNameStr, root, fsInfo.dirSeparator(), fsInfo.isCaseSensitive(), + fsInfo.dirSeparator() + nameStr, "+foo+baz"); + + } + + @ParameterizedTest + @FieldSource("TEST_SUITE") + void documentNameFromFQNameWithBaseName(DocumentName.FSInfo fsInfo) { + final String root = fsInfo.roots()[0]; + final String baseNameStr = root + fsInfo; + String fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + DocumentName documentName = DocumentName.builder(fsInfo).setName(fqName).setBaseName(baseNameStr).build(); + assertDocumentName(documentName, fqName, "foo", baseNameStr, root, fsInfo.dirSeparator(), fsInfo.isCaseSensitive(), + fsInfo.dirSeparator() + "foo", "+foo"); + } + + @ParameterizedTest + @FieldSource("TEST_SUITE") + void noBaseNameThowsException(DocumentName.FSInfo fsInfo) { + final String root = fsInfo.roots()[0]; + String fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + DocumentName.Builder underTest = DocumentName.builder(fsInfo).setName(fqName); + assertThatThrownBy(underTest::build) + .isInstanceOf(NullPointerException.class) + .hasMessage("Basename must not be null"); + } + + @ParameterizedTest + @FieldSource("TEST_SUITE") + void noNameThowsException(DocumentName.FSInfo fsInfo) { + final String root = fsInfo.roots()[0]; + String fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + DocumentName.Builder underTest = DocumentName.builder(fsInfo).setBaseName(fqName); + assertThatThrownBy(underTest::build) + .isInstanceOf(NullPointerException.class) + .hasMessage("Name must not be null"); + } + + @Test + void DocumentNameFromFileSystem() { + FileSystem fileSystem = FileSystems.getDefault(); + String expectedRoot = fileSystem.getRootDirectories().iterator().next().toString(); + DocumentName documentName = DocumentName.builder(fileSystem).setBaseName("") + .setName("theName.txt").build(); + assertThat(documentName.getRoot()).isEqualTo(expectedRoot); + assertThat(documentName.getName()).isEqualTo(expectedRoot + "theName.txt"); + } + + @ParameterizedTest + @FieldSource("TEST_SUITE") + void DocumentNameFromDocumentName(DocumentName.FSInfo fsInfo) { + final String root = fsInfo.roots()[0]; + final String baseNameStr = root + fsInfo; + String fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + DocumentName expected = DocumentName.builder(fsInfo).setName(fqName).setBaseName(baseNameStr).build(); + + DocumentName actual = DocumentName.builder(expected).build(); + assertThat(actual).isEqualTo(expected); + + assertDocumentName(actual, fqName, "foo", baseNameStr, root, fsInfo.dirSeparator(), fsInfo.isCaseSensitive(), + fsInfo.dirSeparator() + "foo", "+foo"); + + } + + @ParameterizedTest + @FieldSource("TEST_SUITE") + void builderOnDocumentNameWithNameSharesBaseName(DocumentName.FSInfo fsInfo) { + final String root = fsInfo.roots()[0]; + final String baseNameStr = root + fsInfo; + + String fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + DocumentName firstName = DocumentName.builder(fsInfo).setName(fqName).setBaseName(baseNameStr).build(); + + fqName = root + fsInfo.mkPath(fsInfo.toString(), "bar"); + DocumentName actual = DocumentName.builder(firstName).setName(fqName).setBaseName(baseNameStr).build(); + + assertDocumentName(actual, fqName, "bar", baseNameStr, root, fsInfo.dirSeparator(), fsInfo.isCaseSensitive(), + fsInfo.dirSeparator() + "bar", "+bar"); + } + + @ParameterizedTest + @FieldSource("TEST_SUITE") + void builderOnFile(DocumentName.FSInfo fsInfo) { + String root = fsInfo.roots()[0]; + final String baseNameStr = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + final String fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo", "bar"); + File file = mock(File.class); + File parent = mock(File.class); + when(file.getAbsolutePath()).thenReturn(fqName); when(file.getParentFile()).thenReturn(parent); when(file.isDirectory()).thenReturn(false); - when(parent.getAbsolutePath()).thenReturn("C:\\windows\\foo"); + when(parent.getAbsolutePath()).thenReturn(baseNameStr); when(parent.isDirectory()).thenReturn(true); - testName = "windows setName(file)"; - documentName = DocumentName.builder(baseName).setName(file).build(); - lst.add(Arguments.of(testName, documentName, "C:\\windows\\foo\\bar", "bar", "C:\\windows\\foo", "C:", "\\", false, - "\\bar", "/bar")); - - file = mock(File.class); - parent = mock(File.class); - when(file.getAbsolutePath()).thenReturn("C:\\windows\\foo\\bar"); - when(file.getParentFile()).thenReturn(parent); - when(file.isDirectory()).thenReturn(true); - when(parent.getAbsolutePath()).thenReturn("C:\\windows\\foo"); - when(parent.isDirectory()).thenReturn(true); - testName = "windows setName(directory)"; - documentName = DocumentName.builder(baseName).setName(file).build(); - lst.add(Arguments.of(testName, documentName, "C:\\windows\\foo\\bar", "bar", "C:\\windows\\foo\\bar", "C:", "\\", false, - "\\", "/")); - return lst.stream(); + DocumentName actual = new DocumentName.Builder(fsInfo, file).build(); + + assertDocumentName(actual, fqName, "bar", baseNameStr, root, fsInfo.dirSeparator(), fsInfo.isCaseSensitive(), + fsInfo.dirSeparator() + "bar", "+bar"); + } + + @Test + void windowRootDifference() { + // verify that setting the root results in the entire DocumentName being re-rooted e.g. including the basename(s). + DocumentName.FSInfo fsInfo = WINDOWS; + String root = fsInfo.roots()[0]; + String baseNameStr = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + String fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo", "bar"); + DocumentName firstName = DocumentName.builder(fsInfo).setName(fqName).setBaseName(baseNameStr).build(); + + root = fsInfo.roots()[1]; + DocumentName actual = DocumentName.builder(firstName).setRoot(root).build(); + + baseNameStr = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo", "bar"); + + assertDocumentName(actual, fqName, "bar", baseNameStr, root, fsInfo.dirSeparator(), fsInfo.isCaseSensitive(), + fsInfo.dirSeparator() + "bar", "+bar"); + + } + + @ParameterizedTest + @FieldSource("TEST_SUITE") + void setRootNull(DocumentName.FSInfo fsInfo) { + for (String root : fsInfo.roots()) { + String baseNameStr = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + String fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo", "bar"); + DocumentName firstName = DocumentName.builder(fsInfo).setName(fqName).setBaseName(baseNameStr).build(); + + // verify setting the root to null results in relative names with a blank root set + DocumentName actual = DocumentName.builder(firstName).setRoot(null).build(); + + assertDocumentName(actual, fqName, "bar", baseNameStr, root, fsInfo.dirSeparator(), fsInfo.isCaseSensitive(), + fsInfo.dirSeparator() + "bar", "+bar"); + } + } + + @ParameterizedTest + @FieldSource("TEST_SUITE") + void setRootEmpty(DocumentName.FSInfo fsInfo) { + for (String baseRoot : fsInfo.roots()) { + String baseNameStr = baseRoot + fsInfo.mkPath(fsInfo.toString(), "foo"); + String fqName = baseRoot + fsInfo.mkPath(fsInfo.toString(), "foo", "bar"); + DocumentName firstName = DocumentName.builder(fsInfo).setName(fqName).setBaseName(baseNameStr).build(); + + DocumentName actual = DocumentName.builder(firstName).setRoot("").build(); + + assertDocumentName(actual, fqName, "bar", baseNameStr, baseRoot, fsInfo.dirSeparator(), fsInfo.isCaseSensitive(), + fsInfo.dirSeparator() + "bar", "+bar"); + } + } + + @ParameterizedTest + @FieldSource("TEST_SUITE") + void splitRootsTest(DocumentName.FSInfo fsInfo) { + for (String root : fsInfo.roots()) { + String path = fsInfo.mkPath("My", "path", "to", "a", "file.txt"); + Pair result = DocumentName.builder(fsInfo).splitRoot(root + path); + assertThat(result.getLeft()).isEqualTo(root); + assertThat(result.getRight()).isEqualTo(path); + } } } diff --git a/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameMatcherTest.java b/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameMatcherTest.java index c53fb23db..c5c616bb2 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameMatcherTest.java +++ b/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameMatcherTest.java @@ -18,21 +18,22 @@ */ package org.apache.rat.document; +import java.util.ArrayList; +import java.util.List; import java.util.function.Predicate; import org.apache.commons.io.filefilter.NameFileFilter; import org.apache.rat.config.exclusion.plexus.MatchPatterns; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import static org.assertj.core.api.Assertions.assertThat; import static org.apache.rat.document.DocumentNameMatcher.MATCHES_ALL; import static org.apache.rat.document.DocumentNameMatcher.MATCHES_NONE; public class DocumentNameMatcherTest { - private final static DocumentNameMatcher TRUE = new DocumentNameMatcher("T", (Predicate)name -> true); private final static DocumentNameMatcher FALSE = new DocumentNameMatcher("F", (Predicate)name -> false); private final static DocumentNameMatcher SOME = new DocumentNameMatcher("X", (Predicate)name -> false); - private final static DocumentName testName = DocumentName.builder().setName("testName").setBaseName("/").build(); public static String processDecompose(DocumentNameMatcher matcher, DocumentName candidate) { StringBuilder sb = new StringBuilder(); @@ -40,24 +41,35 @@ public static String processDecompose(DocumentNameMatcher matcher, DocumentName return sb.toString(); } - @Test - public void orTest() { + private static List testDocuments() { + List args = new ArrayList<>(); + for (DocumentName.FSInfo fsInfo : FSInfoTest.TEST_SUITE) { + args.add(DocumentName.builder(fsInfo).setName("testName").setBaseName("/").build()); + } + return args; + } + + @ParameterizedTest + @MethodSource("testDocuments") + void orTest(DocumentName testName) { assertThat(DocumentNameMatcher.or(TRUE, FALSE).matches(testName)).as("T,F").isTrue(); assertThat(DocumentNameMatcher.or(FALSE, TRUE).matches(testName)).as("F,T").isTrue(); assertThat(DocumentNameMatcher.or(TRUE, TRUE).matches(testName)).as("T,T").isTrue(); assertThat(DocumentNameMatcher.or(FALSE, FALSE).matches(testName)).as("F,F").isFalse(); } - @Test - public void andTest() { + @ParameterizedTest + @MethodSource("testDocuments") + void andTest(DocumentName testName) { assertThat(DocumentNameMatcher.and(TRUE, FALSE).matches(testName)).as("T,F").isFalse(); assertThat(DocumentNameMatcher.and(FALSE, TRUE).matches(testName)).as("F,T").isFalse(); assertThat(DocumentNameMatcher.and(TRUE, TRUE).matches(testName)).as("T,T").isTrue(); assertThat(DocumentNameMatcher.and(FALSE, FALSE).matches(testName)).as("F,F").isFalse(); } - @Test - public void matcherSetTest() { + @ParameterizedTest + @MethodSource("testDocuments") + void matcherSetTest(DocumentName testName) { assertThat(DocumentNameMatcher.matcherSet(TRUE, FALSE).matches(testName)).as("T,F").isTrue(); assertThat(DocumentNameMatcher.matcherSet(FALSE, TRUE).matches(testName)).as("F,T").isFalse(); assertThat(DocumentNameMatcher.matcherSet(TRUE, TRUE).matches(testName)).as("T,T").isTrue(); @@ -76,8 +88,9 @@ public void matcherSetTest() { assertThat(DocumentNameMatcher.matcherSet(SOME, SOME).toString()).as("X,X").isEqualTo("matcherSet(X, X)"); } - @Test - void testDecompose() { + @ParameterizedTest + @MethodSource("testDocuments") + void testDecompose(DocumentName testName) { DocumentNameMatcher matcher1 = new DocumentNameMatcher("FileFilterTest", new NameFileFilter("File.name")); String result = processDecompose(matcher1, testName); assertThat(result).contains("FileFilterTest: >>false<<").contains(" NameFileFilter(File.name)"); diff --git a/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameTest.java b/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameTest.java index beeeeca94..5abac9625 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameTest.java +++ b/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameTest.java @@ -18,79 +18,79 @@ */ package org.apache.rat.document; +import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; +import java.io.FileReader; +import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; -import java.nio.file.FileSystems; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.stream.Stream; -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.tuple.Pair; import org.apache.rat.config.exclusion.ExclusionUtils; import org.apache.rat.document.DocumentName.FSInfo; -import org.assertj.core.util.Files; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.FieldSource; import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.Mockito; import static org.assertj.core.api.Assertions.assertThat; -import static org.apache.rat.document.FSInfoTest.OSX; -import static org.apache.rat.document.FSInfoTest.UNIX; -import static org.apache.rat.document.FSInfoTest.WINDOWS; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.ArgumentMatchers.any; public class DocumentNameTest { - - public static DocumentName mkName(Path tempDir, FSInfo fsInfo) { - File docFile = mkFile(tempDir.toFile(), fsInfo); - DocumentName result = DocumentName.builder(fsInfo).setName(docFile).build(); - DocumentName mocked = Mockito.spy(result); - - String fn = result.localized(FileSystems.getDefault().getSeparator()); - File file = tempDir.resolve(fn.substring(1)).toFile(); - File mockedFile = mkFile(file, fsInfo); - when(mocked.asFile()).thenReturn(mockedFile); - - assertThat(mocked.asFile()).isEqualTo(mockedFile); - return mocked; - } - - private static File[] listFiles(File file, FSInfo fsInfo) { - File[] fileList = file.listFiles(); - if (fileList == null) { - return fileList; - } - return Arrays.stream(fileList).map(f -> mkFile(f, fsInfo)).toArray(File[]::new); + private static final FSInfo[] TEST_SUITE = FSInfoTest.TEST_SUITE; + + /** + * Create a list of mocked files from the specified directory. + * @param directory the native directory to read. + * @param fsInfo the file system to mock the files in. + * @return an array of mocked files in the file system. + */ + private static File[] listFiles(File directory, FSInfo fsInfo) { + File[] fileList = directory.listFiles(); + return fileList == null ? null : Arrays.stream(fileList).map(f -> mkFile(f, fsInfo)).toArray(File[]::new); } - private static File[] listFiles(File file, FSInfo fsInfo, FileFilter filter) { - File[] fileList = file.listFiles(); - if (fileList == null) { - return fileList; - } - return Arrays.stream(fileList).map(f -> mkFile(f, fsInfo)).filter(filter::accept).toArray(File[]::new); + /** + * Create an array of mocked files from the specified directory then apply applying a file filter. + * @param directory the native directory to read files from. + * @param fsInfo the file system to create the mocked files in. + * @param filter the filter to apply to the mocked files. + * @return the array of mocked files that pass the filter. + */ + private static File[] listFiles(File directory, FSInfo fsInfo, FileFilter filter) { + File[] fileList = directory.listFiles(); + return fileList == null ? null : Arrays.stream(fileList).map(f -> mkFile(f, fsInfo)).filter(filter::accept).toArray(File[]::new); } - private static File[] listFiles(File file, FSInfo fsInfo, FilenameFilter filter) { - File[] fileList = file.listFiles(); - if (fileList == null) { - return fileList; - } - return Arrays.stream(fileList).map(f -> mkFile(f, fsInfo)).filter(x -> filter.accept(x, x.getName())).toArray(File[]::new); + /** + * Create an array of mocked files from the specified directory then apply applying a file filter. + * @param directory the native directory to read files from. + * @param fsInfo the file system to create the mocked files in. + * @param filter the filter to apply to the mocked files. + * @return the array of mocked files that pass the filter. + */ + private static File[] listFiles(File directory, FSInfo fsInfo, FilenameFilter filter) { + File[] fileList = directory.listFiles(); + return fileList == null ? null : Arrays.stream(fileList).map(f -> mkFile(f, fsInfo)).filter(x -> filter.accept(x, x.getName())).toArray(File[]::new); } + /** + * Creates a mocked file on the specified file system with the + * @param file the name of the native file. + * @param fsInfo the file system to mock the file in. + * @return the mocked file in the specified file system + */ public static File mkFile(final File file, final FSInfo fsInfo) { File mockedFile = mock(File.class); when(mockedFile.listFiles()).thenAnswer( env -> listFiles(file, fsInfo)); @@ -105,222 +105,199 @@ public static File mkFile(final File file, final FSInfo fsInfo) { return mockedFile; } - public static DocumentName mkName(Path tempDir, DocumentName baseDir, String pth) throws IOException { - DocumentName result = baseDir.resolve(ExclusionUtils.convertSeparator(pth, "/", baseDir.getDirectorySeparator())); - DocumentName mocked = Mockito.spy(result); - - String fn = result.localized(FileSystems.getDefault().getSeparator()); - File file = tempDir.resolve(fn.substring(1)).toFile(); - File parent = file.getParentFile(); - if (parent.exists() && !parent.isDirectory()) { - parent.delete(); - } - parent.mkdirs(); - if (file.exists()) { - if (file.isDirectory()) { - FileUtils.deleteDirectory(file); - } else { - FileUtils.delete(file); - } - } - file.createNewFile(); - when(mocked.asFile()).thenReturn(file); - return mocked; - } - - @ParameterizedTest(name = "{index} {0} {2}") + /** + * Verifies that {@code resolve()} works correctly. + * @param fsInfo the file system under test. + * @param base the DocumentName to resolve from. + * @param toResolve the string to resolve. + * @param expected the expected DocumentName after resolution. + */ + @ParameterizedTest(name = "{index} {0} {1} {3}") @MethodSource("resolveTestData") - void resolveTest(String testName, DocumentName base, String toResolve, DocumentName expected) { + void resolveTest(DocumentName.FSInfo fsInfo, String root, DocumentName base, String toResolve, DocumentName expected) { DocumentName actual = base.resolve(toResolve); - assertThat(actual).isEqualTo(expected); + assertThat(actual.getName()).isEqualTo(expected.getName()); } private static Stream resolveTestData() { List lst = new ArrayList<>(); + DocumentName base; + DocumentName expected; + for (DocumentName.FSInfo fsInfo : TEST_SUITE) { + String root = fsInfo.roots()[0]; + for (String baseName : List.of(root, root + fsInfo.mkPath("from", "base"))) { + for (String testRoot : fsInfo.roots()) { + String name = testRoot + fsInfo.mkPath("", "dir", fsInfo.toString()); + base = DocumentName.builder(fsInfo).setName(name).setBaseName(baseName).build(); + lst.add(Arguments.of(fsInfo, base.getName(), base, null, base)); - DocumentName base = DocumentName.builder(UNIX).setName("/dir/unix").setBaseName("/").build(); - - DocumentName expected = DocumentName.builder(UNIX).setName("/dir/unix/relative").setBaseName("/").build(); - lst.add(Arguments.of("unix", base, "relative", expected)); + lst.add(Arguments.of(fsInfo, base.getName(), base, "", base)); - expected = DocumentName.builder(UNIX).setName("/from/root").setBaseName("/").build(); - lst.add(Arguments.of("unix", base, "/from/root", expected)); + lst.add(Arguments.of(fsInfo, base.getName(), base, " ", base)); - expected = DocumentName.builder(UNIX).setName("dir/up/and/down").setBaseName("/").build(); - lst.add(Arguments.of("unix", base, "../up/and/down", expected)); + expected = DocumentName.builder(fsInfo).setName(fsInfo.mkPath(name, "relative")).setBaseName(baseName).build(); + lst.add(Arguments.of(fsInfo, base.getName(), base, "relative", expected)); - expected = DocumentName.builder(UNIX).setName("/from/root").setBaseName("/").build(); - lst.add(Arguments.of("unix", base, "\\from\\root", expected)); + expected = DocumentName.builder(fsInfo).setRoot(testRoot).setName(fsInfo.mkPath("", "from", "root")).setBaseName(baseName).build(); + lst.add(Arguments.of(fsInfo, base.getName(), base, fsInfo.mkPath("", "from", "root"), expected)); - expected = DocumentName.builder(UNIX).setName("dir/up/and/down").setBaseName("/").build(); - lst.add(Arguments.of("unix", base, "..\\up\\and\\down", expected)); + expected = DocumentName.builder(fsInfo).setRoot(testRoot).setName(fsInfo.mkPath("dir", "up", "and", "down")).setBaseName(baseName).build(); + lst.add(Arguments.of(fsInfo, base.getName(), base, fsInfo.mkPath("..", "up", "and", "down"), expected)); - // WINDOWS - base = DocumentName.builder(WINDOWS).setName("\\dir\\windows").setBaseName("C:\\").build(); - - expected = DocumentName.builder(WINDOWS).setName("\\dir\\windows\\relative").setBaseName("C:\\").build(); - lst.add(Arguments.of("windows", base, "relative", expected)); - - expected = DocumentName.builder(WINDOWS).setName("\\from\\root").setBaseName("C:\\").build(); - lst.add(Arguments.of("windows", base, "/from/root", expected)); - - expected = DocumentName.builder(WINDOWS).setName("dir\\up\\and\\down").setBaseName("C:\\").build(); - lst.add(Arguments.of("windows", base, "../up/and/down", expected)); - - expected = DocumentName.builder(WINDOWS).setName("\\from\\root").setBaseName("C:\\").build(); - lst.add(Arguments.of("windows", base, "\\from\\root", expected)); - - expected = DocumentName.builder(WINDOWS).setName("dir\\up\\and\\down").setBaseName("C:\\").build(); - lst.add(Arguments.of("windows", base, "..\\up\\and\\down", expected)); - - // OSX - base = DocumentName.builder(OSX).setName("/dir/osx").setBaseName("/").build(); - - expected = DocumentName.builder(OSX).setName("/dir/osx/relative").setBaseName("/").build(); - lst.add(Arguments.of("osx", base, "relative", expected)); - - expected = DocumentName.builder(OSX).setName("/from/root").setBaseName("/").build(); - lst.add(Arguments.of("osx", base, "/from/root", expected)); - - expected = DocumentName.builder(OSX).setName("dir/up/and/down").setBaseName("/").build(); - lst.add(Arguments.of("osx", base, "../up/and/down", expected)); - - expected = DocumentName.builder(OSX).setName("/from/root").setBaseName("/").build(); - lst.add(Arguments.of("osx", base, "\\from\\root", expected)); - - expected = DocumentName.builder(OSX).setName("dir/up/and/down").setBaseName("/").build(); - lst.add(Arguments.of("osx", base, "..\\up\\and\\down", expected)); + expected = DocumentName.builder(fsInfo).setRoot(testRoot).setName(fsInfo.mkPath("", "from", "root")).setBaseName(baseName).build(); + String wrongSeparator = fsInfo.dirSeparator().equals("/") ? "\\" : "/"; + lst.add(Arguments.of(fsInfo, base.getName(), base, String.join(wrongSeparator, "", "from", "root"), expected)); + expected = DocumentName.builder(fsInfo).setRoot(testRoot).setName(fsInfo.mkPath("dir", "up", "and", "down")).setBaseName(baseName).build(); + lst.add(Arguments.of(fsInfo, base.getName(), base, String.join(wrongSeparator, "..", "up", "and", "down"), expected)); + } + } + } return lst.stream(); } @Test - void localizeTest() { - DocumentName documentName = DocumentName.builder(UNIX).setName("/a/b/c") - .setBaseName("/a").build(); - assertThat(documentName.localized()).isEqualTo("/b/c"); - assertThat(documentName.localized("-")).isEqualTo("-b-c"); + void resolveWithMultipleRootsTest() { + // these tests exist because windows has multiple roots. + DocumentName.FSInfo fsInfo = FSInfoTest.WINDOWS; + DocumentName base = DocumentName.builder(fsInfo).setName(fsInfo.mkPath("", "dir", fsInfo.toString())) + .setBaseName("").build(); + + String resolveName = fsInfo.roots()[1] + fsInfo.mkPath("dir", fsInfo.toString()); + assertThatThrownBy(() -> base.resolve(resolveName)) + .as(resolveName) + .hasMessageContaining(String.format("%s does not start with %s", resolveName, base.getName())) + .isInstanceOf(IllegalArgumentException.class); + + String resolveName2 = fsInfo.roots()[0] + fsInfo.mkPath("dir", fsInfo.toString(), "thing"); + assertThat(base.resolve(resolveName2).getName()) + .isEqualTo(resolveName2); - documentName = DocumentName.builder(WINDOWS).setName("\\a\\b\\c") - .setBaseName("\\a").build(); - assertThat(documentName.localized()).isEqualTo("\\b\\c"); - assertThat(documentName.localized("-")).isEqualTo("-b-c"); + } - documentName = DocumentName.builder(OSX).setName("/a/b/c") - .setBaseName("/a").build(); - assertThat(documentName.localized()).isEqualTo("/b/c"); - assertThat(documentName.localized("-")).isEqualTo("-b-c"); + void testNoRootSpecified() { + // no root specified + assertThat(DocumentName.startsWithRootOrSeparator("X:/candidate", null, "/")).isFalse(); + assertThat(DocumentName.startsWithRootOrSeparator("X:/candidate", "", "/")).isFalse(); + assertThat(DocumentName.startsWithRootOrSeparator("X;/candidate", " ", "/")).isFalse(); + assertThat(DocumentName.startsWithRootOrSeparator("/candidate", null, "/")).isTrue(); + assertThat(DocumentName.startsWithRootOrSeparator("/candidate", "", "/")).isTrue(); + assertThat(DocumentName.startsWithRootOrSeparator("/candidate", " ", "/")).isTrue(); + assertThat(DocumentName.startsWithRootOrSeparator("candidate", null, "/")).isFalse(); + assertThat(DocumentName.startsWithRootOrSeparator("candidate", "", "/")).isFalse(); + assertThat(DocumentName.startsWithRootOrSeparator("candidate", " ", "/")).isFalse(); } - @ParameterizedTest(name = "{index} {0}") - @MethodSource("validBuilderData") - void validBuilderTest(String testName, DocumentName.Builder builder, String root, String name, String baseName, String dirSeparator) { - DocumentName underTest = builder.build(); - assertThat(underTest.getRoot()).as(testName).isEqualTo(root); - assertThat(underTest.getDirectorySeparator()).as(testName).isEqualTo(dirSeparator); - assertThat(underTest.getName()).as(testName).isEqualTo(root + dirSeparator + name); - assertThat(underTest.getBaseName()).as(testName).isEqualTo(root + dirSeparator + baseName); + @Test + void startsWithRootOrSeparatorTest() { + assertThat(DocumentName.startsWithRootOrSeparator("X:/candidate", "X:/", "/")).isTrue(); + assertThat(DocumentName.startsWithRootOrSeparator("X:/candidate", "Y:/", "/")).isFalse(); + assertThat(DocumentName.startsWithRootOrSeparator("/candidate", "X:/", "/")).isTrue(); + assertThat(DocumentName.startsWithRootOrSeparator("\\candidate", "Y:/", "/")).isFalse(); + assertThat(DocumentName.startsWithRootOrSeparator("candidate", "Y:/", "/")).isFalse(); + assertThat(DocumentName.startsWithRootOrSeparator("Y:candidate", "Y:/", "/")).isFalse(); + assertThat(DocumentName.startsWithRootOrSeparator(null, "Y:/", "/")).isFalse(); + assertThat(DocumentName.startsWithRootOrSeparator("", "Y:/", "/")).isFalse(); + assertThat(DocumentName.startsWithRootOrSeparator(" ", "Y:/", "/")).isFalse(); + + testNoRootSpecified(); } - private static Stream validBuilderData() { - List lst = new ArrayList<>(); - File f = Files.newTemporaryFile(); - - Set roots = new HashSet<>(); - File[] rootary = File.listRoots(); - if (rootary != null) { - for (File root : rootary) { - String name = root.getPath(); - roots.add(name); - } - } + @ParameterizedTest + @FieldSource("TEST_SUITE") + void localizeTest(FSInfo fsInfo) { + DocumentName documentName = DocumentName.builder(fsInfo).setName( + fsInfo.mkPath("", "a", "b", "c")) + .setBaseName(fsInfo.mkPath("", "a")).build(); - String name = f.getAbsolutePath(); - String root = ""; - for (String sysRoot : roots) { - if (name.startsWith(sysRoot)) { - name = name.substring(sysRoot.length()); - if (sysRoot.endsWith(File.separator)) { - root = sysRoot.substring(0, sysRoot.length() - File.separator.length()); - } - break; - } - } - - File p = f.getParentFile(); - String baseName = p.getAbsolutePath().substring(root.length()); - if (baseName.startsWith(File.separator)) { - baseName = baseName.substring(File.separator.length()); - } - lst.add(Arguments.of("setName(file)", DocumentName.builder().setName(f), root, name, baseName, File.separator)); - lst.add(Arguments.of("Builder(file)", DocumentName.builder(f), root, name, baseName, File.separator)); + assertThat(documentName.localized()).isEqualTo(fsInfo.mkPath("", "b", "c")); + assertThat(documentName.localized("-")).isEqualTo("-b-c"); - lst.add(Arguments.of("setName(dir)", DocumentName.builder().setName(p), root, baseName, baseName, File.separator)); - lst.add(Arguments.of("Builder(dir)", DocumentName.builder(p), root, baseName, baseName, File.separator)); + documentName = DocumentName.builder(fsInfo).setName( + fsInfo.mkPath("", "a", "b", "c")) + .setBaseName(fsInfo.mkPath("", "z")).build(); - File r = new File(root.isEmpty() ? File.separator : root); - lst.add(Arguments.of("setName(root)", DocumentName.builder().setName(r), root, "", "", File.separator)); - lst.add(Arguments.of("Builder(root)", DocumentName.builder(r), root, "", "", File.separator)); + assertThat(documentName.localized()).isEqualTo(fsInfo.mkPath("", "a", "b", "c")); + if (fsInfo.roots().length > 1) { + documentName = DocumentName.builder(fsInfo).setName( + fsInfo.mkPath("", "a", "b", "c")) + .setBaseName(fsInfo.mkPath("", "z")) + .setRoot(fsInfo.roots()[1]).build(); + assertThat(documentName.localized()).isEqualTo(fsInfo.mkPath("", "a", "b", "c")); + } + } - lst.add(Arguments.of("foo/bar foo", DocumentName.builder(UNIX) - .setName("/foo/bar").setBaseName("foo"), "", "foo/bar", "foo", "/")); + @Test + void asFileTest() throws IOException { + File expected = File.createTempFile("docNameTest", ".txt"); + try (FileWriter fw = new FileWriter(expected, StandardCharsets.UTF_8)) { + fw.write("Hello world"); + } + DocumentName underTest = DocumentName.builder(expected).build(); + File actual = underTest.asFile(); + try (FileReader fr = new FileReader(actual, StandardCharsets.UTF_8); + BufferedReader br = new BufferedReader(fr)) { + assertThat(br.readLine()).isEqualTo("Hello world"); + } + } - DocumentName.Builder builder = DocumentName.builder(WINDOWS).setName("\\foo\\bar").setBaseName("C:\\foo") - .setRoot("C:"); - lst.add(Arguments.of("\\foo\\bar foo", builder, "C:", "foo\\bar", "foo", "\\")); + @Test + void asPathTest() throws IOException { + File expected = File.createTempFile("docNameTest", ".txt"); + try (FileWriter fw = new FileWriter(expected, StandardCharsets.UTF_8)) { + fw.write("Hello world"); + } + DocumentName underTest = DocumentName.builder(expected).build(); + Path actual = underTest.asPath(); + Path root = Path.of(underTest.getRoot()); + File file = root.resolve(actual).toFile(); + try (FileReader fr = new FileReader(file, StandardCharsets.UTF_8); + BufferedReader br = new BufferedReader(fr)) { + assertThat(br.readLine()).isEqualTo("Hello world"); + } + } - lst.add(Arguments.of("foo/bar foo", DocumentName.builder(OSX) - .setName("/foo/bar").setBaseName("foo"), "", "foo/bar", "foo", "/")); - return lst.stream(); + @ParameterizedTest(name = "{index} {0} {1}") + @MethodSource("archiveEntryTestData") + void archiveEntryNameTest(String os, String testName, DocumentName archiveName, String root, String separator, String baseName, + String localizedName) { + assertThat(archiveName.getRoot()).as("root").isEqualTo(root); + assertThat(archiveName.getDirectorySeparator()).as("separator").isEqualTo(separator); + assertThat(archiveName.getBaseName()).as("baseName").isEqualTo(baseName); + assertThat(archiveName.localized()).as("localized").isEqualTo(localizedName); + assertThat(archiveName.getName()).as("name").isEqualTo(baseName + localizedName); + if (!separator.equals(archiveName.fsInfo().dirSeparator())) + { + String newBaseName = separator.equals("/") ? baseName.replace('\\', '/') : baseName.replace('/', '\\'); + assertThat(archiveName.localized(separator)).as("localized(x)").isEqualTo(newBaseName + localizedName); + } } - @Test - void splitRootsTest() { - Pair result = DocumentName.builder(WINDOWS).splitRoot("C:\\My\\path\\to\\a\\file.txt"); - assertThat(result.getLeft()).isEqualTo("C:"); - assertThat(result.getRight()).isEqualTo("My\\path\\to\\a\\file.txt"); - - result = DocumentName.builder(UNIX).splitRoot("/My/path/to/a/file.txt"); - assertThat(result.getLeft()).isEqualTo(""); - assertThat(result.getRight()).isEqualTo("My/path/to/a/file.txt"); - - result = DocumentName.builder(OSX).splitRoot("/My/path/to/a/file.txt"); - assertThat(result.getLeft()).isEqualTo(""); - assertThat(result.getRight()).isEqualTo("My/path/to/a/file.txt"); - } + static List archiveEntryTestData() { + List lst = new ArrayList<>(); - @Test - void archiveEntryNameTest() { - String entryName = "./anArchiveEntry.txt"; - DocumentName archiveName = DocumentName.builder(WINDOWS) - .setName("C:\\archives\\anArchive.zip").setBaseName("C:\\archives").build(); - - assertThat(archiveName.getRoot()).isEqualTo("C:"); - assertThat(archiveName.getDirectorySeparator()).isEqualTo("\\"); - assertThat(archiveName.getBaseName()).isEqualTo("C:\\archives"); - assertThat(archiveName.getName()).isEqualTo("C:\\archives\\anArchive.zip"); - assertThat(archiveName.localized()).isEqualTo("\\anArchive.zip"); - - ArchiveEntryName archiveEntryName = new ArchiveEntryName(archiveName, entryName); - - assertThat(archiveEntryName.getRoot()).isEqualTo(archiveName.getName()+"#"); - assertThat(archiveEntryName.getDirectorySeparator()).isEqualTo("/"); - assertThat(archiveEntryName.getBaseName()).isEqualTo("C:\\archives\\anArchive.zip#"); - assertThat(archiveEntryName.getName()).isEqualTo("C:\\archives\\anArchive.zip#/anArchiveEntry.txt"); - assertThat(archiveEntryName.localized()).isEqualTo("/anArchiveEntry.txt"); - assertThat(archiveEntryName.localized("/")).isEqualTo("/anArchive.zip#/anArchiveEntry.txt"); - - // test with directory - entryName = "./someDir/anArchiveEntry.txt"; - archiveEntryName = new ArchiveEntryName(archiveName, entryName); - - assertThat(archiveEntryName.getRoot()).isEqualTo(archiveName.getName()+"#"); - assertThat(archiveEntryName.getDirectorySeparator()).isEqualTo("/"); - assertThat(archiveEntryName.getBaseName()).isEqualTo("C:\\archives\\anArchive.zip#"); - assertThat(archiveEntryName.getName()).isEqualTo("C:\\archives\\anArchive.zip#/someDir/anArchiveEntry.txt"); - assertThat(archiveEntryName.localized()).isEqualTo("/someDir/anArchiveEntry.txt"); - assertThat(archiveEntryName.localized("/")).isEqualTo("/anArchive.zip#/someDir/anArchiveEntry.txt"); + for (FSInfo fsInfo : FSInfoTest.TEST_SUITE) { + String os = fsInfo.toString(); + String root = fsInfo.roots()[0]; + String baseName = String.format(String.format("%sarchives", root)); + String simpleName = String.format("%sanArchive.zip", fsInfo.dirSeparator()); + String entryName = "./anArchiveEntry.txt"; + DocumentName archiveName = DocumentName.builder(fsInfo).setName(baseName + simpleName).setBaseName(baseName).build(); + lst.add(Arguments.of(os, "archive name", archiveName, root, fsInfo.dirSeparator(), baseName, simpleName)); + + ArchiveEntryName archiveEntryName = new ArchiveEntryName(archiveName, entryName); + baseName = archiveName.getName() + "#"; + root = baseName + "/"; + lst.add(Arguments.of(os, "archive entry name", archiveEntryName, root, "/", baseName, "/anArchiveEntry.txt")); + + // test with directory + entryName = "./someDir/anArchiveEntry.txt"; + archiveEntryName = new ArchiveEntryName(archiveName, entryName); + + lst.add(Arguments.of(os, "archive entry with directory", archiveEntryName, root, "/", baseName, "/someDir/anArchiveEntry.txt")); + } + return lst; } } diff --git a/apache-rat-core/src/test/java/org/apache/rat/document/FSInfoTest.java b/apache-rat-core/src/test/java/org/apache/rat/document/FSInfoTest.java index ced9d8c62..e2513822e 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/document/FSInfoTest.java +++ b/apache-rat-core/src/test/java/org/apache/rat/document/FSInfoTest.java @@ -23,9 +23,19 @@ import java.io.IOException; import java.nio.file.FileSystem; import java.nio.file.FileSystems; -import java.util.Arrays; -import java.util.stream.Stream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.FieldSource; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class FSInfoTest { public static final DocumentName.FSInfo DEFAULT; @@ -36,7 +46,8 @@ public class FSInfoTest { static { try (FileSystem osx = Jimfs.newFileSystem(Configuration.osX()); FileSystem unix = Jimfs.newFileSystem(Configuration.unix()); - FileSystem windows = Jimfs.newFileSystem(Configuration.windows())) { + FileSystem windows = Jimfs.newFileSystem(Configuration.windows().toBuilder() + .setRoots("C:\\", "D:\\").build())) { OSX = new DocumentName.FSInfo("osx", osx); UNIX = new DocumentName.FSInfo("unix", unix); WINDOWS = new DocumentName.FSInfo("windows", windows); @@ -48,11 +59,96 @@ public class FSInfoTest { public static final DocumentName.FSInfo[] TEST_SUITE = {UNIX, WINDOWS, OSX}; - /** - * Provides arguments for parameterized tests that only require the fsInfo. - * @return a stream of TEST_SUITE based Arguments. - */ - public static Stream fsInfoArgs() { - return Arrays.stream(TEST_SUITE).map(Arguments::of); + @ParameterizedTest + @FieldSource("TEST_SUITE") + void normalizeTest(DocumentName.FSInfo info) { + String sep = info.dirSeparator(); + Map strings = new TreeMap<>(); + strings.put("hello", "hello"); + strings.put("hello/", "hello"); + strings.put("/hello", sep+"hello"); + strings.put("/hello/../goodbye", sep+"goodbye"); + strings.put("hello/../goodbye", "goodbye"); + strings.put("/hello/./goodbye", sep+"hello"+sep+"goodbye"); + strings.put("hello/./goodbye", "hello"+sep+"goodbye"); + strings.put("\\hello\\..\\goodbye", sep+"goodbye"); + strings.put("hello\\..\\goodbye", "goodbye"); + strings.put("\\hello\\.\\goodbye", sep+"hello"+sep+"goodbye"); + strings.put("hello\\.\\goodbye", "hello"+sep+"goodbye"); + + strings.put(info.roots()[0]+"hello", info.roots()[0]+"hello"); + String pattern = "a/b\\c"; + strings.put(pattern, pattern.replace(sep.equals("/") ? "\\" : "/", sep)); + strings.put("", ""); + strings.put(" ", ""); + strings.put(".", ""); + strings.put(" .", ""); + strings.put(" . ", ""); + strings.put("/", sep); + strings.put("\\", sep); + + assertThat(info.normalize(null)).as("[null]").isEmpty(); + for (Map.Entry entry : strings.entrySet()) { + assertThat(info.normalize(entry.getKey())).as(String.format("[%s]", entry.getKey())) + .isEqualTo(entry.getValue()); + } + + for (String pattern2 : List.of( "/..", "hello/../..", "/hello/../..")) { + assertThatThrownBy(() -> info.normalize(pattern2)).as(String.format("[%s]", pattern2)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Unable to create path before root"); + } + } + + static List tokenizingData() { + List data = new ArrayList<>(); + for (DocumentName.FSInfo fsInfo : TEST_SUITE) { + String sep = fsInfo.dirSeparator(); + String notSep = sep.equals("/") ? "\\" : "/"; + data.add(Arguments.of(fsInfo, "hello", List.of("hello"))); + data.add(Arguments.of(fsInfo, "hello" + sep, List.of("hello"))); + data.add(Arguments.of(fsInfo, "hello" + sep + "world", List.of("hello", "world"))); + data.add(Arguments.of(fsInfo, sep + "hello", List.of("", "hello"))); + data.add(Arguments.of(fsInfo, sep + "hello" + sep + ".." + sep + "goodbye", List.of("", "hello", "..", "goodbye"))); + data.add(Arguments.of(fsInfo, "hello" + sep + ".." + sep + "goodbye", List.of("hello", "..", "goodbye"))); + data.add(Arguments.of(fsInfo, notSep + "hello" + sep + "goodbye", List.of(notSep + "hello", "goodbye"))); + } + return data; + } + + @ParameterizedTest + @MethodSource("tokenizingData") + void tokenizeTest(DocumentName.FSInfo info, String token, List split) { + assertThat(info.tokenize(token)) + .containsExactlyElementsOf(split); + } + + @ParameterizedTest + @MethodSource("tokenizingData") + void mkPathTest(DocumentName.FSInfo info, String path, List segments) { + if (path.endsWith(info.dirSeparator())) { + assertThat(info.mkPath(segments.toArray(new String[0]))).isEqualTo(path.substring(0, path.length()-1)); + } else { + assertThat(info.mkPath(segments.toArray(new String[0]))).isEqualTo(path); + } + } + + @Test + void compareTests() { + for (int i = 0; i < TEST_SUITE.length; i++) { + for (int j = 0; j < TEST_SUITE.length; j++) { + DocumentName.FSInfo underTest = TEST_SUITE[i]; + DocumentName.FSInfo compareTo = TEST_SUITE[j]; + String name = String.format("%s with %s ", underTest, compareTo); + if (i == j) { + assertThat(underTest).as(name + "equality").isEqualTo(compareTo); + assertThat(underTest.hashCode()).as(name + "hashCode").hasSameHashCodeAs(compareTo); + assertThat(underTest).as(name + "compareTo").isEqualByComparingTo(compareTo); + } else { + assertThat(underTest).as(name + "equality").isNotEqualTo(compareTo); + assertThat(underTest).as(name + "compareTo").isNotEqualByComparingTo(compareTo); + } + } + } } } diff --git a/apache-rat-core/src/test/java/org/apache/rat/test/AbstractConfigurationOptionsProvider.java b/apache-rat-core/src/test/java/org/apache/rat/test/AbstractConfigurationOptionsProvider.java index 6c4aa2ab5..721fd0deb 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/test/AbstractConfigurationOptionsProvider.java +++ b/apache-rat-core/src/test/java/org/apache/rat/test/AbstractConfigurationOptionsProvider.java @@ -18,6 +18,7 @@ */ package org.apache.rat.test; +import java.io.FileWriter; import java.nio.file.FileSystems; import java.nio.file.Path; import org.apache.commons.cli.Option; @@ -25,6 +26,7 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.text.WordUtils; import org.apache.rat.OptionCollectionTest; import org.apache.rat.ReportConfiguration; import org.apache.rat.ReporterTest; @@ -37,6 +39,7 @@ import org.apache.rat.license.ILicenseFamily; import org.apache.rat.license.LicenseSetFactory; import org.apache.rat.report.claim.ClaimStatistic; +import org.apache.rat.report.xml.writer.XmlWriter; import org.apache.rat.test.utils.Resources; import org.apache.rat.testhelpers.TextUtils; import org.apache.rat.utils.DefaultLog; @@ -54,11 +57,13 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Locale; import java.util.SortedSet; import static org.apache.rat.commandline.Arg.HELP_LICENSES; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.fail; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; /** * A list of methods that an OptionsProvider in a test case must support. @@ -75,7 +80,7 @@ public abstract class AbstractConfigurationOptionsProvider extends AbstractOptio */ public static void preserveData(File baseDir, String targetDir) { final Path recordPath = FileSystems.getDefault().getPath("target", targetDir); - recordPath.toFile().mkdirs(); + org.apache.rat.testhelpers.FileUtils.mkDir(recordPath.toFile()); try { FileUtils.copyDirectory(baseDir, recordPath.toFile()); } catch (IOException e) { @@ -229,10 +234,10 @@ protected void inputExcludeParsedScmTest() { writeFile(".gitignore", Arrays.asList(lines)); File dir = new File(baseDir, "red"); - dir.mkdirs(); + org.apache.rat.testhelpers.FileUtils.mkDir(dir); dir = new File(baseDir, "blue"); dir = new File(dir, "fish"); - dir.mkdirs(); + org.apache.rat.testhelpers.FileUtils.mkDir(dir); try { ReportConfiguration config = generateConfig(ImmutablePair.of(option, args)); @@ -525,8 +530,55 @@ protected void counterMinTest() { } } + private String writeConfigXML(String name) { + String licenseText = """ + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You 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."""; + String capName = WordUtils.capitalize(name); + String upperName = name.toUpperCase(Locale.ROOT); + + Path xmlPath = this.baseDir.toPath().resolve(".rat/" + capName + ".xml"); + File f = xmlPath.toFile(); + try { + FileUtils.forceMkdir(f.getParentFile()); + try (FileWriter writer = new FileWriter(f); + XmlWriter xmlWriter = new XmlWriter(writer)) { + + xmlWriter.startDocument() + .comment(licenseText) + .openElement("rat-config") + .openElement("families") + .openElement("family") + .attribute("id", upperName) + .attribute("name", "from " + capName + ".xml") + .closeElement("families") + .openElement("licenses") + .openElement("license") + .attribute("family", upperName) + .openElement("text") + .content(name) + .closeElement("rat-config"); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return xmlPath.toString(); + } + private void configTest(final Option option) { - String[] args = {"src/test/resources/OptionTools/One.xml", "src/test/resources/OptionTools/Two.xml"}; + String[] args = {writeConfigXML("one"), writeConfigXML("two")}; Pair arg1 = ImmutablePair.of(option, args); try { ReportConfiguration config = generateConfig(arg1); @@ -556,14 +608,12 @@ protected void configTest() { } private void noDefaultsTest(final Option arg) { - try { + assertDoesNotThrow(() -> { ReportConfiguration config = generateConfig(ImmutablePair.of(arg, null)); assertThat(config.getLicenses(LicenseSetFactory.LicenseFilter.ALL)).isEmpty(); config = generateConfig(ImmutablePair.nullPair()); assertThat(config.getLicenses(LicenseSetFactory.LicenseFilter.ALL)).isNotEmpty(); - } catch (IOException e) { - fail(e.getMessage()); - } + }); } protected void noDefaultsTest() { @@ -594,11 +644,10 @@ private void editCopyrightTest(final Option option) { config = generateConfig(arg1, arg2); assertThat(config.getCopyrightMessage()).isEqualTo("MyCopyright"); } catch (IOException e) { - e.printStackTrace(); if (e.getCause() != null) { fail(e.getMessage() + ": " + e.getCause().getMessage()); } - fail(e.getMessage()); + fail(e.getMessage(), e); } } @@ -780,7 +829,11 @@ private void styleSheetTest(final Option option) { try ( InputStream in = ReporterTest.class.getResourceAsStream("MatcherContainerResource.txt"); OutputStream out = Files.newOutputStream(file.toPath())) { - IOUtils.copy(in, out); + if (in == null) { + fail("Could not copy MatcherContainerResource.txt: resource not found"); + } else { + IOUtils.copy(in, out); + } } catch (IOException e) { fail("Could not copy MatcherContainerResource.txt: " + e.getMessage()); } diff --git a/apache-rat-tools/src/main/java/org/apache/rat/documentation/options/AntOptionCollection.java b/apache-rat-tools/src/main/java/org/apache/rat/documentation/options/AntOptionCollection.java index 8b8182ba1..eb5930716 100644 --- a/apache-rat-tools/src/main/java/org/apache/rat/documentation/options/AntOptionCollection.java +++ b/apache-rat-tools/src/main/java/org/apache/rat/documentation/options/AntOptionCollection.java @@ -34,6 +34,7 @@ import org.apache.commons.text.WordUtils; import org.apache.rat.OptionCollection; import org.apache.rat.commandline.Arg; +import org.apache.rat.document.DocumentName; import org.apache.rat.ui.ArgumentTracker; import org.apache.rat.ui.UIOptionCollection; import org.apache.rat.utils.CasedString; @@ -78,6 +79,7 @@ public final class AntOptionCollection extends UIOptionCollection { ATTRIBUTE_TYPES.add(Integer.class); ATTRIBUTE_TYPES.add(Long.class); ATTRIBUTE_TYPES.add(File.class); + ATTRIBUTE_TYPES.add(DocumentName.class); } public static Map getRenameMap() { diff --git a/src/changes/changes.xml b/src/changes/changes.xml index cd8a80e55..03dce8ca3 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -68,6 +68,9 @@ in order to be properly linked in site reports. --> + + Fix issues with DocumentName and DocumentNameBuilder that address errors in archive handling and Windows as operating system. + Disallowed DOCTYPE declarations in the Xerces2 XML parser to mitigate XXE attacks, and centralised XML parser initialisation within RAT.