Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions FileDeduplicator.Test/FileScannerAndHasherTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,25 @@ public void ScanOfAMissingDirectoryReturnsNothing()
Assert.IsEmpty(files);
}

/// <summary>
/// A path to a file, rather than the folder holding it, must return an empty list rather than
/// throwing, so pasting a file path does not crash the tool.
/// </summary>
[TestMethod]
public void ScanOfAFileReturnsNothing()
{
// Arrange
using TempTree tree = new();
AbsoluteFilePath file = tree.Write("a/f.txt", "content");
AbsoluteDirectoryPath notADirectory = file.WeakString.As<AbsoluteDirectoryPath>();

// Act
IReadOnlyList<AbsoluteFilePath> files = FileScanner.ScanForFiles(notADirectory);

// Assert
Assert.IsEmpty(files);
}

/// <summary>
/// One unreadable directory must not abort the scan. Enumeration is lazy, so the refusal
/// arrives partway through the walk, after files elsewhere in the tree have already been found
Expand Down
29 changes: 29 additions & 0 deletions FileDeduplicator.Test/VerbOutputTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,35 @@ private static void WriteOneGroup(TempTree tree, out AbsoluteFilePath keeper, ou
_ = tree.Write("unique.txt", "beta");
}

/// <summary>
/// Every verb given a file instead of a directory says so in one line and deletes nothing.
/// </summary>
[TestMethod]
public void EveryVerbReportsAFilePathAsNotADirectory()
{
// Arrange
using TempTree tree = new();
AbsoluteFilePath file = tree.Write("a/f.txt", "content");
string path = file.WeakString;

// Act
string[] outputs =
[
ConsoleCapture.Run(new Scan { PathString = path }),
ConsoleCapture.Run(new DryRun { PathString = path }),
ConsoleCapture.Run(new Stats { PathString = path }),
ConsoleCapture.Run(new Deduplicate { PathString = path }, "y"),
];

// Assert
foreach (string output in outputs)
{
Assert.Contains($"Not a directory: {path}", output);
}

Assert.IsTrue(TempTree.Exists(file), "The file named as the root must be left alone.");
}

/// <summary>
/// DryRun names the keeper and every other copy, totals them, and deletes nothing.
/// </summary>
Expand Down
11 changes: 10 additions & 1 deletion FileDeduplicator/FileScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,16 @@ internal static class FileScanner

internal static IReadOnlyList<AbsoluteFilePath> ScanForFiles(AbsoluteDirectoryPath path)
{
if (!path.Exists)
// path.Exists is also true for a file, which Directory.EnumerateFiles then rejects with an
// unhandled DirectoryNotFoundException. A path tab-completed one level too far is an easy
// mistake, so it gets the same one-line error as a path that is not there at all.
if (File.Exists(path.WeakString))
{
Console.WriteLine($"Not a directory: {path}");
return [];
}

if (!Directory.Exists(path.WeakString))
{
Console.WriteLine($"Directory not found: {path}");
return [];
Expand Down
Loading